From 7ddead3f30ea50adc762d55ec6a32568427e692c Mon Sep 17 00:00:00 2001 From: Srikanth Sankaran <131454720+srikanth-sankaran@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:35:02 +0530 Subject: [PATCH] [Patterns] Unconditional pattern in instanceof shouldn't compile at compliance level of 17 (#3547) * Fixes https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3478 --- .../eclipse/jdt/core/compiler/IProblem.java | 3 +- .../compiler/ast/InstanceOfExpression.java | 7 +- .../internal/compiler/ast/TryStatement.java | 3 +- .../internal/compiler/lookup/TypeBinding.java | 3 +- .../compiler/problem/ProblemReporter.java | 11 +++- .../InstanceofPrimaryPatternTest.java | 24 ++++++- .../regression/PatternMatching16Test.java | 55 ++++++++++++---- .../internal/codeassist/DOMCodeSelector.java | 66 ++++--------------- .../codeassist/complete/CompletionParser.java | 3 +- 9 files changed, 97 insertions(+), 78 deletions(-) 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 289c958a32c..83bb063f0e0 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 @@ -2526,9 +2526,8 @@ public interface IProblem { */ int PatternVariableRedefined = Internal + 1781; /** @since 3.26 - * @deprecated */ - int PatternSubtypeOfExpression = Internal + 1782; + int PatternSubtypeOfExpression = Internal + 1782; // ass backwards naming of API constant :-( It should read ExpressionSubtypeOfPattern /** @since 3.26 */ int IllegalModifierForPatternVariable = Internal + 1783; 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 352210da4d3..87953685bb0 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,15 +277,16 @@ public TypeBinding resolveType(BlockScope scope) { if (expressionType == null || checkedType == null) return null; + CompilerOptions options = scope.compilerOptions(); if (this.pattern != null) { - if (this.pattern.isApplicable(expressionType, scope, this)) { + if (this.pattern.isApplicable(expressionType, scope, this)) checkForPrimitives(scope, checkedType, expressionType); - } + if (options.complianceLevel < ClassFileConstants.JDK21 && expressionType.isSubtypeOf(checkedType, false)) + scope.problemReporter().expressionTypeCannotBeSubtypeOfPatternType(this.expression); return this.resolvedType = TypeBinding.BOOLEAN; } if (!checkedType.isReifiable()) { - CompilerOptions options = scope.compilerOptions(); // Report same as before for older compliances if (options.complianceLevel < ClassFileConstants.JDK16) { scope.problemReporter().illegalInstanceOfGenericType(checkedType, this); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TryStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TryStatement.java index 632f903810b..8d349e7f5c1 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TryStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TryStatement.java @@ -1012,7 +1012,8 @@ public void resolve(BlockScope upperScope) { // special scope for secret locals optimization. this.scope = new BlockScope(upperScope); - if (enclosingSwitchExpression(upperScope) instanceof SwitchExpression swich) { + SwitchExpression swich; + if ((swich = enclosingSwitchExpression(upperScope)) != null) { swich.jvmStackVolatile = true; // ought to prepare for any raised exception blowing up the the operand stack to smithereens } BlockScope finallyScope = null; 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 fec8c4bbbf7..552dbbbb3de 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 @@ -728,7 +728,8 @@ public boolean isRecord() { } public boolean isRecordWithComponents() { // do records without components make sense ??! - return isRecord() && components() instanceof RecordComponentBinding [] components && components.length > 0; + RecordComponentBinding [] components; + return isRecord() && (components = components()) != null && components.length > 0; } /* Answer true if the receiver type can be assigned to the argument type (right) 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 50ee30d6e68..b8020efc207 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 @@ -2166,6 +2166,14 @@ public void illegalRedeclarationOfPatternVar(LocalVariableBinding local, ASTNode nodeSourceStart(local, location), nodeSourceEnd(local, location)); } +public void expressionTypeCannotBeSubtypeOfPatternType(ASTNode location) { + this.handle( + IProblem.PatternSubtypeOfExpression, // ass backwards naming of API constant :-( + NoArgument, + NoArgument, + location.sourceStart, + location.sourceEnd); +} public void duplicateMethodInType(AbstractMethodDeclaration methodDecl, boolean equalParameters, int severity) { MethodBinding method = methodDecl.binding; if (equalParameters) { @@ -6412,7 +6420,8 @@ public void nullAnnotationUnsupportedLocation(TypeReference type) { } private char[][] missingAnalysisAnnotationName(AnnotationBinding[] annotations, LookupEnvironment environment) { for (AnnotationBinding annotationBinding : annotations) { - if (annotationBinding.getAnnotationType() instanceof ReferenceBinding type + ReferenceBinding type; + if ((type = annotationBinding.getAnnotationType()) != null && environment.checkForMissingAnalysisAnnotation(type) != 0) return type.compoundName; } 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 97553036bd2..3a9a0bb4786 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 @@ -202,12 +202,26 @@ public void test009() { " }\n" + "}\n", }, + this.complianceLevel < ClassFileConstants.JDK21 ? + "----------\n" + - "1. ERROR in X.java (at line 4)\n" + + "1. ERROR in X.java (at line 3)\n" + + " if (s instanceof Object o) {\n" + + " ^\n" + + "Expression type cannot be a subtype of the Pattern type\n" + + "----------\n" + + "2. ERROR in X.java (at line 4)\n" + " System.out.println(s1);\n" + " ^^\n" + "s1 cannot be resolved to a variable\n" + - "----------\n"); + "----------\n" : + + "----------\n" + + "1. ERROR in X.java (at line 4)\n" + + " System.out.println(s1);\n" + + " ^^\n" + + "s1 cannot be resolved to a variable\n" + + "----------\n"); } // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1076 // ECJ accepts invalid Java code instanceof final Type @@ -652,6 +666,8 @@ private void foo(String x) { // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3222 // [Patterns][Ternary] Pattern binding variable not recognized in poly conditional operator expression public void testIssue3222() { + if (this.complianceLevel < ClassFileConstants.JDK21) + return; runConformTest( new String[] { "X.java", @@ -679,6 +695,8 @@ public static void main(String[] args) { // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3222 // [Patterns][Ternary] Pattern binding variable not recognized in poly conditional operator expression public void testIssue3222_2() { + if (this.complianceLevel < ClassFileConstants.JDK21) + return; runConformTest( new String[] { "X.java", @@ -706,6 +724,8 @@ public static void main(String[] args) { // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3222 // [Patterns][Ternary] Pattern binding variable not recognized in poly conditional operator expression public void testIssue3222_3() { + if (this.complianceLevel < ClassFileConstants.JDK21) + return; runConformTest( new String[] { "X.java", 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 b68104b0d76..9dbf444d1b0 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 @@ -1038,7 +1038,7 @@ public void test019b() { options); } /* Test that we report subtypes of pattern variables used in the same stmt - * As of Java 19, we no longer report error for the above + * As of Java 21, we no longer report error for the above */ public void test020() { Map options = getCompilerOptions(true); @@ -1055,12 +1055,24 @@ public void test020() { " }\n" + "}\n", }, - "----------\n" + - "1. ERROR in X20.java (at line 7)\n" + - " System.out.print(b1);\n" + - " ^^\n" + - "b1 cannot be resolved to a variable\n" + - "----------\n", + this.complianceLevel < ClassFileConstants.JDK21 ? + "----------\n" + + "1. ERROR in X20.java (at line 6)\n" + + " boolean b = (o instanceof String[] s) && s instanceof CharSequence[] s2;\n" + + " ^\n" + + "Expression type cannot be a subtype of the Pattern type\n" + + "----------\n" + + "2. ERROR in X20.java (at line 7)\n" + + " System.out.print(b1);\n" + + " ^^\n" + + "b1 cannot be resolved to a variable\n" + + "----------\n" : + "----------\n" + + "1. ERROR in X20.java (at line 7)\n" + + " System.out.print(b1);\n" + + " ^^\n" + + "b1 cannot be resolved to a variable\n" + + "----------\n", "", null, true, @@ -2431,12 +2443,24 @@ public void testBug562392d() { " }\n" + "}\n", }, - "----------\n" + - "1. ERROR in X.java (at line 10)\n" + - " System.out.println(abc);\n" + - " ^^^\n" + - "abc cannot be resolved to a variable\n" + - "----------\n", + this.complianceLevel < ClassFileConstants.JDK21 ? + "----------\n" + + "1. ERROR in X.java (at line 4)\n" + + " if (null instanceof T t) {\n" + + " ^^^^\n" + + "Expression type cannot be a subtype of the Pattern type\n" + + "----------\n" + + "2. ERROR in X.java (at line 10)\n" + + " System.out.println(abc);\n" + + " ^^^\n" + + "abc cannot be resolved to a variable\n" + + "----------\n" : + "----------\n" + + "1. ERROR in X.java (at line 10)\n" + + " System.out.println(abc);\n" + + " ^^^\n" + + "abc cannot be resolved to a variable\n" + + "----------\n", "", null, true, @@ -4327,7 +4351,8 @@ public static void main(String [] args) { // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1485 // ECJ hangs when pattern matching code is used in a nested conditional expression. public void testGHI1485() { - + if (this.complianceLevel < ClassFileConstants.JDK21) + return; runConformTest( new String[] { "X.java", @@ -4823,6 +4848,8 @@ public static void main(String [] args) { // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2104 // [Patterns] Missing boxing conversion after instanceof leads to verify error public void testBoxing() { + if (this.complianceLevel < ClassFileConstants.JDK21) + return; runConformTest( new String[] { "X.java", 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 index f9c77b4ee8f..fafdcb31a64 100644 --- 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 @@ -20,54 +20,9 @@ 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.*; +import org.eclipse.jdt.core.dom.*; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; @@ -245,10 +200,11 @@ public IJavaElement[] codeSelect(int offset, int length) throws JavaModelExcepti } else if (findTypeDeclaration(node) == null) { IBinding binding = resolveBinding(node); if (binding != null && !binding.isRecovered()) { + ITypeBinding declaringClass; if (node instanceof SuperMethodInvocation && // on `super` binding instanceof IMethodBinding methodBinding && - methodBinding.getDeclaringClass() instanceof ITypeBinding typeBinding && - typeBinding.getJavaElement() instanceof IType type) { + (declaringClass = methodBinding.getDeclaringClass()) != null && + declaringClass.getJavaElement() instanceof IType type) { return new IJavaElement[] { type }; } if (binding instanceof IPackageBinding packageBinding @@ -262,11 +218,13 @@ public IJavaElement[] codeSelect(int offset, int length) throws JavaModelExcepti } } // workaround https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2177 + IMethodBinding declaringMethod; + ITypeBinding recordBinding; if (binding instanceof IVariableBinding variableBinding && - variableBinding.getDeclaringMethod() instanceof IMethodBinding declaringMethod && + (declaringMethod = variableBinding.getDeclaringMethod()) != null && declaringMethod.isCompactConstructor() && Arrays.stream(declaringMethod.getParameterNames()).anyMatch(variableBinding.getName()::equals) && - declaringMethod.getDeclaringClass() instanceof ITypeBinding recordBinding && + (recordBinding = declaringMethod.getDeclaringClass()) != null && recordBinding.isRecord() && recordBinding.getJavaElement() instanceof IType recordType && recordType.getField(variableBinding.getName()) instanceof SourceField field) { @@ -315,10 +273,11 @@ public IJavaElement[] codeSelect(int offset, int length) throws JavaModelExcepti } } } + IField field; if (binding instanceof IMethodBinding methodBinding && methodBinding.isSyntheticRecordMethod() && methodBinding.getDeclaringClass().getJavaElement() instanceof IType recordType && - recordType.getField(methodBinding.getName()) instanceof IField field) { + (field = recordType.getField(methodBinding.getName())) != null) { return new IJavaElement[] { field }; } ASTNode bindingNode = currentAST.findDeclaringNode(binding); @@ -367,6 +326,7 @@ public IJavaElement[] codeSelect(int offset, int length) throws JavaModelExcepti return new IJavaElement[] { currentElement }; } if (insideComment) { + IType type; String toSearch = trimmedText.isBlank() ? findWord(offset) : trimmedText; String resolved = ((List)currentAST.imports()).stream() .map(org.eclipse.jdt.core.dom.ImportDeclaration::getName) @@ -374,7 +334,7 @@ public IJavaElement[] codeSelect(int offset, int length) throws JavaModelExcepti .filter(importedPackage -> importedPackage.endsWith(toSearch)) .findAny() .orElse(toSearch); - if (this.unit.getJavaProject().findType(resolved) instanceof IType type) { + if ((type = this.unit.getJavaProject().findType(resolved)) != null) { return new IJavaElement[] { type }; } } diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java index 6d6e18e5236..bed58f8aeb3 100644 --- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java +++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java @@ -1249,7 +1249,8 @@ private boolean popBlockContaining(ASTNode soughtStatement) { RecoveredElement elem = this.currentElement; while (elem instanceof RecoveredBlock block) { for (int i=0; i