diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java index a65e75a821..3aa24b53fa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java @@ -382,25 +382,20 @@ public final class TypeOps { private static JTypeMirror upperBound(JTypeMirror type) { if (type instanceof JWildcardType) { - return upperBound(((JWildcardType) type).asUpperBound()); - } else if (type instanceof JTypeVar && ((JTypeVar) type).isCaptured()) { - return upperBound(((JTypeVar) type).getUpperBound()); + return ((JWildcardType) type).asUpperBound(); } return type; } private static JTypeMirror lowerBound(JTypeMirror type) { if (type instanceof JWildcardType) { - return lowerBound(((JWildcardType) type).asLowerBound()); - } else if (type instanceof JTypeVar && ((JTypeVar) type).isCaptured()) { - return lowerBound(((JTypeVar) type).getLowerBound()); + return ((JWildcardType) type).asLowerBound(); } return type; } private static boolean isTypeRange(JTypeMirror s) { - return s instanceof JWildcardType - || s instanceof JTypeVar && ((JTypeVar) s).isCaptured(); + return s instanceof JWildcardType; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java index 9e6a61bd3b..42c97c0c99 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java @@ -97,11 +97,11 @@ final class ExprCheckHelper { // now if the return type of the arg is polymorphic and unsolved, // there are some additional bounds on our own infCtx - // FIXME if the argument is not compatible we'll fail resolution - // altogether, though if we're in invocation phase we should - // preserve the CT-decl checker.checkExprConstraint(infCtx, mostSpecific.getReturnType(), targetType); + + // fixme this fails to set the inferred type of arguments + // if we skip invocation on the outer expr if (phase.isInvocation()) { infCtx.addInstantiationListener( infCtx.freeVarsIn(mostSpecific.getReturnType()), diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java index 565a49c569..435e4ba1fe 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java @@ -448,7 +448,7 @@ final class ExprOps { TypeSystem ts = sig.getTypeSystem(); if ("getClass".equals(sig.getName()) && sig.getDeclaringType().equals(ts.OBJECT)) { if (erasedReceiverType != null) { - return sig.internalApi().withReturnType(getClassReturn(erasedReceiverType, ts)); + return sig.internalApi().withReturnType(getClassReturn(erasedReceiverType, ts)).internalApi().markAsAdapted(); } } return sig; 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 6e27959888..d5d930ad9d 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 @@ -247,13 +247,11 @@ public final class Infer { if (isReturnTypeFinished(m)) { assert assertReturnIsGround(m); - // todo this appears duplicated - m = ExprOps.adaptGetClass(m, site.getExpr().getErasedReceiverType()); - LOG.skipInstantiation(m, site); expr.setInferredType(m.getReturnType()); - if (site.areAllArgsRelevantToApplicability()) { + // fixme the type of subexpressions might not have been set + if (false && site.areAllArgsRelevantToApplicability()) { // then all have been inferred return ctdecl; } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TypeTestUtil.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TypeTestUtil.kt index addba203a0..d86994f732 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TypeTestUtil.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TypeTestUtil.kt @@ -6,11 +6,13 @@ package net.sourceforge.pmd.lang.java.types +import io.kotest.matchers.shouldBe import io.kotest.property.Arb import io.kotest.property.Exhaustive import io.kotest.property.RandomSource import io.kotest.property.Sample import net.sourceforge.pmd.lang.ast.test.shouldBe +import net.sourceforge.pmd.lang.ast.test.shouldBeA import net.sourceforge.pmd.lang.java.ast.ASTTypeParameter import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters import net.sourceforge.pmd.lang.java.ast.JavaNode @@ -59,6 +61,14 @@ fun JTypeVar.withNewBounds(upper: JTypeMirror? = null, lower:JTypeMirror? = null this.cloneWithBounds(upper ?: this.upperBound, lower ?: this.lowerBound) } +fun JTypeMirror.shouldBeCaptureOf(wild: JWildcardType) = + this.shouldBeA { + it.isCaptured shouldBe true + if (wild.isLowerBound) + it.lowerBound shouldBe wild.asLowerBound() + else + it.upperBound shouldBe wild.asUpperBound() + } @Suppress("ObjectPropertyName", "MemberVisibilityCanBePrivate") class TypeGen(override val ts: TypeSystem) : Arb(), TypeDslMixin { @@ -125,6 +135,7 @@ class TypeGen(override val ts: TypeSystem) : Arb(), TypeDslMixin { /** raw Comparable */ val t_Comparable: JClassType get() = java.lang.Comparable::class.raw + val t_Comparator: JClassType get() = java.util.Comparator::class.raw val t_EnumSet: JClassType get() = java.util.EnumSet::class.raw diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/CaptureInferenceTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/CaptureInferenceTest.kt index 419bbb5072..1408eac064 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/CaptureInferenceTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/CaptureInferenceTest.kt @@ -4,12 +4,16 @@ package net.sourceforge.pmd.lang.java.types.internal.infer +import io.kotest.inspectors.forExactly +import io.kotest.matchers.collections.shouldBeSingleton import io.kotest.matchers.shouldBe import net.sourceforge.pmd.lang.ast.test.shouldBe +import net.sourceforge.pmd.lang.ast.test.shouldBeA import net.sourceforge.pmd.lang.ast.test.shouldMatchN import net.sourceforge.pmd.lang.java.ast.* +import net.sourceforge.pmd.lang.java.types.* import net.sourceforge.pmd.lang.java.types.testdata.TypeInferenceTestCases -import net.sourceforge.pmd.lang.java.types.typeDsl +import java.util.function.ToIntFunction /** * @author Clément Fournier @@ -83,6 +87,75 @@ class CaptureInferenceTest : ProcessorTestSpec({ } } + parserTest("Test method ref on captured thing") { + + logTypeInference(verbose = true) + + val acu = parser.parse(""" + import java.util.List; + import java.util.ArrayList; + import java.util.Comparator; + + class Scratch { + private List sortIt(final List stats) { + final List statList = new ArrayList<>(stats); + statList.sort(Comparator.comparingInt(Object::hashCode)); + return statList; + } + } + + """.trimIndent()) + + val call = acu.descendants(ASTMethodCall::class.java).first()!! + + call.shouldMatchN { + methodCall("sort") { + it::getTypeMirror shouldBe with(it.typeDsl) { ts.NO_TYPE } + + variableAccess("statList") {} + argList { + var capture: JTypeVar? = null + methodCall("comparingInt") { + with (it.typeDsl) { + // eg. java.util.Comparator + val ret = it.typeMirror.shouldBeA { + it.symbol shouldBe gen.t_Comparator.symbol + it.typeArgs.shouldBeSingleton { + capture = it.shouldBeCaptureOf(`?` extends gen.t_String) + } + } + + it.methodType.shouldMatchMethod( + named = "comparingInt", + declaredIn = gen.t_Comparator, + withFormals = listOf(ToIntFunction::class[`?` `super` capture!!]), + returning = ret + ) + } + + + typeExpr { + classType("Comparator") + } + + argList { + methodRef("hashCode") { + typeExpr { + classType("Object") + } + + with(it.typeDsl) { + it.referencedMethod shouldBe ts.OBJECT.getMethodsByName("hashCode").single() + it.typeMirror shouldBe ToIntFunction::class[capture!!] + } + } + } + } + } + } + } + } + }) /** diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/TestUtils.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/TestUtils.kt index 2f70388c4d..8d0c73732f 100644 --- a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/TestUtils.kt +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/TestUtils.kt @@ -4,16 +4,11 @@ package net.sourceforge.pmd.lang.ast.test -import kotlin.reflect.KCallable -import kotlin.reflect.jvm.isAccessible -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe as ktShouldBe -import io.kotest.matchers.should import io.kotest.matchers.Matcher import io.kotest.matchers.equalityMatcher -import io.kotest.matchers.collections.haveSize -import java.util.stream.Stream -import kotlin.streams.toList +import io.kotest.matchers.should +import kotlin.reflect.KCallable +import kotlin.reflect.jvm.isAccessible /** * Extension to add the name of a property to error messages.