diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md
index e64354df24..c1d07eb028 100644
--- a/docs/pages/release_notes.md
+++ b/docs/pages/release_notes.md
@@ -25,6 +25,8 @@ This is a {{ site.pmd.release_type }} release.
* [#5293](https://github.com/pmd/pmd/issues/5293): \[java] Deadlock when executing PMD in multiple threads
* [#5324](https://github.com/pmd/pmd/issues/5324): \[java] Issue with type inference of nested lambdas
* [#5329](https://github.com/pmd/pmd/issues/5329): \[java] Type inference issue with unknown method ref in call chain
+* java-bestpractices
+ * [#5083](https://github.com/pmd/pmd/issues/5083): \[java] UnusedPrivateMethod false positive when method reference has no target type
* java-performance
* [#5314](https://github.com/pmd/pmd/issues/5314): \[java] InsufficientStringBufferDeclarationRule: Lack of handling for char type parameters
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java
index 7982304210..e51ef905f2 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java
@@ -36,7 +36,6 @@ import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.CtorInvocat
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.FunctionalExprMirror;
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror;
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror.MethodCtDecl;
-import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.LambdaExprMirror;
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.MethodRefMirror;
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.PolyExprMirror;
import net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind;
@@ -145,13 +144,19 @@ public final class Infer {
LOG.logResolutionFail(rfe.getFailure());
// here we set expected if not null, the lambda will have the target type
expr.setInferredType(expected == null ? ts.UNKNOWN : expected);
+ expr.setFunctionalMethod(ts.UNRESOLVED_METHOD);
if (expr instanceof MethodRefMirror) {
MethodRefMirror mref = (MethodRefMirror) expr;
- mref.setFunctionalMethod(ts.UNRESOLVED_METHOD);
+ if (!TypeOps.isUnresolved(mref.getTypeToSearch())) {
+ JMethodSig exactMethod = ExprOps.getExactMethod(mref);
+ if (exactMethod != null) {
+ // as a fallback, if the method reference is exact,
+ // we populate the compile time decl anyway.
+ mref.setCompileTimeDecl(exactMethod);
+ return;
+ }
+ }
mref.setCompileTimeDecl(ts.UNRESOLVED_METHOD);
- } else {
- LambdaExprMirror lambda = (LambdaExprMirror) expr;
- lambda.setFunctionalMethod(ts.UNRESOLVED_METHOD);
}
}
}
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/MethodRefInferenceTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/MethodRefInferenceTest.kt
index e928dda72b..ef10cf77df 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/MethodRefInferenceTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/MethodRefInferenceTest.kt
@@ -5,12 +5,12 @@
package net.sourceforge.pmd.lang.java.types.internal.infer
import io.kotest.matchers.shouldBe
+import net.sourceforge.pmd.lang.java.ast.*
+import net.sourceforge.pmd.lang.java.types.*
import net.sourceforge.pmd.lang.test.ast.component6
import net.sourceforge.pmd.lang.test.ast.component7
import net.sourceforge.pmd.lang.test.ast.shouldBe
import net.sourceforge.pmd.lang.test.ast.shouldMatchN
-import net.sourceforge.pmd.lang.java.ast.*
-import net.sourceforge.pmd.lang.java.types.*
import java.util.*
import java.util.function.*
import java.util.stream.Collector
@@ -331,12 +331,13 @@ class MethodRefInferenceTest : ProcessorTestSpec({
val t_Archive = acu.firstTypeSignature()
val mref = acu.descendants(ASTMethodReference::class.java).firstOrThrow()
+ val (getName) = acu.declaredMethodSignatures().toList()
val call = acu.firstMethodCall()
spy.shouldHaveMissingCtDecl(call)
acu.withTypeDsl {
- mref.referencedMethod shouldBe ts.UNRESOLVED_METHOD
+ mref.referencedMethod shouldBe getName
mref shouldHaveType ts.UNKNOWN
call.methodType shouldBe ts.UNRESOLVED_METHOD
call.overloadSelectionInfo.apply {
@@ -1093,4 +1094,38 @@ class Scratch {
mref shouldHaveType t_Additioner
}
}
+
+ parserTest("Method ref without target type still populates CTDecl if exact") {
+ val (acu, spy) = parser.parseWithTypeInferenceSpy(
+ """
+ class GenerationType {
+ static {
+ foo(GenerationType::isAndroidType);
+ }
+ {
+ foo(this::instance);
+ }
+
+ private boolean instance(String data) {}
+ private static boolean isAndroidType(String data) {
+ return "android".equals(data);
+ }
+ }
+ """.trimIndent()
+ )
+
+ val (instance, static) = acu.declaredMethodSignatures()
+
+ val mrefs = acu.descendants(ASTMethodReference::class.java).toList()
+ val (staticMref, instanceMref) = mrefs
+
+ spy.shouldBeOk {
+ mrefs.forEach {
+ it shouldHaveType ts.UNKNOWN
+ it.functionalMethod shouldBe ts.UNRESOLVED_METHOD
+ }
+ instanceMref.referencedMethod shouldBe instance
+ staticMref.referencedMethod shouldBe static
+ }
+ }
})
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt
index 45c13cf569..7a8b14a2af 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt
@@ -530,6 +530,7 @@ class C {
val (mref) = acu.descendants(ASTMethodReference::class.java).toList()
val (lambdaCall, mrefCall) = acu.descendants(ASTMethodCall::class.java).toList()
+ val (fooDecl) = acu.declaredMethodSignatures().toList()
spy.shouldHaveNoApplicableMethods(lambdaCall)
spy.shouldHaveNoApplicableMethods(mrefCall)
@@ -540,7 +541,7 @@ class C {
mref shouldHaveType ts.UNKNOWN
mref.functionalMethod shouldBe ts.UNRESOLVED_METHOD
- mref.referencedMethod shouldBe ts.UNRESOLVED_METHOD
+ mref.referencedMethod shouldBe fooDecl // still populated because unambiguous
}
}
@@ -562,6 +563,7 @@ class C {
val (lambda) = acu.descendants(ASTLambdaExpression::class.java).toList()
val (mref) = acu.descendants(ASTMethodReference::class.java).toList()
+ val (fooDecl) = acu.declaredMethodSignatures().toList()
spy.shouldHaveNoLambdaCtx(lambda)
spy.shouldHaveNoLambdaCtx(mref)
@@ -572,7 +574,7 @@ class C {
mref shouldHaveType ts.UNKNOWN
mref.functionalMethod shouldBe ts.UNRESOLVED_METHOD
- mref.referencedMethod shouldBe ts.UNRESOLVED_METHOD
+ mref.referencedMethod shouldBe fooDecl // still populated because unambiguous
}
}
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml
index ae0fc5e242..12b92436d9 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml
@@ -2237,5 +2237,80 @@ public class ObtainViaTest {
}
]]>
+
+ UnusedPrivateMethod #5083 - method reference without target type
+ 0
+ predicate;
+
+ private static boolean isAppleType(String data) {
+ return "apple".equals(data);
+ }
+
+ private static boolean isRokuType(String data) {
+ return "roku".equals(data);
+ }
+
+ private static boolean isSamsungType(String data) {
+ return "samsung".equals(data);
+ }
+
+ private static boolean isAmazonType(String data) {
+ return "amazon".equals(data);
+ }
+
+ private static boolean isAndroidType(String data) {
+ return "android".equals(data);
+ }
+ }
+ ]]>
+
+
+
+ UnusedPrivateMethod #5083 - method reference without target type (2)
+ 0
+
+
+
+
+ UnusedPrivateMethod #5083 - method reference without target type (3)
+ 2
+ s) {
+ s.forEach(this::foo); // Not being able to resolve UnresolvedType and AnotherUnresolvedType means we can't tell which foo is being used hereā¦
+ }
+}
+ ]]>
+