From 9219fcb3066d8c5cda3788c9d12f33c8e195ef41 Mon Sep 17 00:00:00 2001 From: Snjezana Peco Date: Wed, 3 Jan 2024 16:03:06 +0100 Subject: [PATCH] Incorrect unused import warning Fixes #1808 The issue happens for imports used in the permits clause when that import is - declared as a nested class - is used only in a permits clause The fix checks if member types are imported and marks the import as used. Signed-off-by: Snjezana Peco --- .../internal/compiler/lookup/ClassScope.java | 23 ++++++++++ .../jdt/internal/compiler/lookup/Scope.java | 24 ++++++++++- .../regression/BatchCompilerTest_17.java | 42 +++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) 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 884e8308905..c53d9022f54 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 @@ -47,6 +47,7 @@ import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.RecordComponent; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -1701,6 +1702,28 @@ private ReferenceBinding findPermittedtype(TypeReference typeReference) { } typeReference.bits |= ASTNode.IgnoreRawTypeCheck; ReferenceBinding permittedType = (ReferenceBinding) typeReference.resolveType(this); + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1808#issuecomment-1918455864 + if ( !(typeReference instanceof QualifiedTypeReference) && permittedType != null && permittedType.isMemberType() && permittedType.enclosingType() != null && permittedType.enclosingType().isInterface()) { + String name = CharOperation.toString(permittedType.compoundName); + name = name.replace("$", "."); //$NON-NLS-1$ //$NON-NLS-2$ + char[][] compoundName = CharOperation.splitOn('.', name.toCharArray()); + ImportBinding[] imports = unitScope.imports; + boolean resolved = false; + for (ImportBinding importBinding: imports) { + if (CharOperation.equals(compoundName, importBinding.compoundName)) { + resolved = true; + break; + } + } + if (!resolved) { + MissingTypeBinding miss = new MissingTypeBinding(permittedType.fPackage, compoundName, + env); + typeReference.resolvedType = miss; + typeReference.resolveType(this); + problemReporter().invalidType(typeReference, miss); + return miss; + } + } return permittedType; } catch (AbortCompilation e) { SourceTypeBinding sourceType = this.referenceContext.binding; 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 4c51fc8e8bb..69d0dc6cdb5 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 @@ -3457,7 +3457,7 @@ final Binding getTypeOrPackage(char[] name, int mask, boolean needResolve) { if (memberType.problemId() == ProblemReasons.Ambiguous) { if (foundType == null || foundType.problemId() == ProblemReasons.NotVisible) // supercedes any potential InheritedNameHidesEnclosingName problem - return memberType; + return checkUsed(name, scope, memberType); // make the user qualify the type, likely wants the first inherited type return new ProblemReferenceBinding(new char[][]{name}, foundType, ProblemReasons.InheritedNameHidesEnclosingName); } @@ -3468,7 +3468,7 @@ final Binding getTypeOrPackage(char[] name, int mask, boolean needResolve) { // found a valid type in the 'immediate' scope (i.e. not inherited) // OR in 1.4 mode (inherited visible shadows enclosing) if (foundType == null || (inheritedHasPrecedence && foundType.problemId() == ProblemReasons.NotVisible)) - return memberType; + return checkUsed(name, scope, memberType); // if a valid type was found, complain when another is found in an 'immediate' enclosing type (i.e. not inherited) if (foundType.isValidBinding() && TypeBinding.notEquals(foundType, memberType)) return new ProblemReferenceBinding(new char[][]{name}, foundType, ProblemReasons.InheritedNameHidesEnclosingName); @@ -3656,6 +3656,26 @@ final Binding getTypeOrPackage(char[] name, int mask, boolean needResolve) { return foundType; } + private Binding checkUsed(char[] name, Scope scope, ReferenceBinding memberType) { + CompilationUnitScope unitScope = scope.compilationUnitScope(); + ImportBinding[] imports = unitScope.imports; + HashtableOfObject typeOrPackageCache = unitScope.typeOrPackageCache; + if (imports != null && typeOrPackageCache == null) { // walk single type imports since faultInImports() has not run yet + for (int i = 0, length = imports.length; i < length; i++) { + ImportBinding importBinding = imports[i]; + if (!importBinding.onDemand && CharOperation.equals(importBinding.getSimpleName(), name)) { + Binding resolvedImport = unitScope.resolveSingleImport(importBinding, Binding.TYPE); + if (resolvedImport instanceof TypeBinding) { + ImportReference importReference = importBinding.reference; + if (importReference != null && !isUnnecessarySamePackageImport(importBinding.resolvedImport, unitScope)) + importReference.bits |= ASTNode.Used; + } + } + } + } + return memberType; + } + private boolean isUnnecessarySamePackageImport(Binding resolvedImport, Scope unitScope) { if (resolvedImport instanceof ReferenceBinding) { ReferenceBinding referenceBinding = (ReferenceBinding) resolvedImport; diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest_17.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest_17.java index ab1697a6400..ce43927a540 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest_17.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest_17.java @@ -147,4 +147,46 @@ boolean match(String err) { return "StdErr: " + error + " StdOut: " + output; } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1808 + public void testGH1808() { + this.runConformTest( + new String[] { + "p/X.java", + """ + package foo; + import foo.X.B; + public sealed interface X permits B { + record B(int data) implements X {} + } + """ + }, + "\"" + OUTPUT_DIR + File.separator + "p" + File.separator + "X.java\"" + + " -17 -g -preserveAllLocals -proceedOnError -referenceInfo ", + "", + "", + true); + } + public void testGH1808a() { + this.runNegativeTest( + new String[] { + "p/X.java", + """ + package foo; + public sealed interface X permits B { + record B(int data) {} + } + """ + }, + "\"" + OUTPUT_DIR + File.separator + "p" + File.separator + "X.java\"" + + " -17 -g -preserveAllLocals -proceedOnError -referenceInfo ", + "", + "----------\n" + + "1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/p/X.java (at line 2)\n" + + " public sealed interface X permits B {\n" + + " ^\n" + + "B cannot be resolved to a type\n" + + "----------\n" + + "1 problem (1 error)\n", + true); + } }