Uast: making ULambdaExpression.functionalInterfaceType return non-null values for explicitly typed SAM-s with Java interfaces (KT-28272)

This commit is contained in:
Nicolay Mitropolsky
2019-02-18 17:24:40 +03:00
parent a326ba60a3
commit d7584df01b
4 changed files with 148 additions and 48 deletions
@@ -33,7 +33,9 @@ import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.synthetic.SyntheticMemberDescriptor
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment
import org.jetbrains.kotlin.load.java.sam.SamAdapterDescriptor
import org.jetbrains.kotlin.load.java.sam.SamConstructorDescriptor
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
@@ -41,6 +43,7 @@ import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -54,6 +57,7 @@ import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor
import org.jetbrains.kotlin.type.MapPsiToAsmDesc
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isInterface
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.uast.*
import java.lang.ref.WeakReference
import java.text.StringCharacterIterator
@@ -311,15 +315,16 @@ internal fun KotlinType.getFunctionalInterfaceType(source: UElement, element: Kt
internal fun KotlinULambdaExpression.getFunctionalInterfaceType(): PsiType? {
val parent = psi.parent
if (parent is KtBinaryExpressionWithTypeRHS) return parent.right?.getType()?.getFunctionalInterfaceType(this, psi)
if (parent is KtLambdaArgument) run {
val callExpression = parent.parent as? KtCallExpression ?: return@run
if (parent is KtValueArgument) run {
val callExpression = parent.parents.take(2).firstIsInstanceOrNull<KtCallExpression>() ?: return@run
val resolvedCall = callExpression.getResolvedCall(callExpression.analyze()) ?: return@run
val candidateDescriptor = resolvedCall.candidateDescriptor
val candidateDescriptor = resolvedCall.candidateDescriptor as? SyntheticMemberDescriptor<*> ?: return@run
when (candidateDescriptor) {
is SamConstructorDescriptor -> return candidateDescriptor.returnType?.getFunctionalInterfaceType(this, psi)
is SamAdapterExtensionFunctionDescriptor -> {
is SamAdapterDescriptor<*>, is SamAdapterExtensionFunctionDescriptor -> {
val index = (resolvedCall.getArgumentMapping(parent) as? ArgumentMatch)?.valueParameter?.index ?: return@run
val parameterDescriptor = candidateDescriptor.baseDescriptorForSynthetic.valueParameters.getOrNull(index) ?: return@run
val functionDescriptor = candidateDescriptor.baseDescriptorForSynthetic as? FunctionDescriptor ?: return@run
val parameterDescriptor = functionDescriptor.valueParameters.getOrNull(index) ?: return@run
return parameterDescriptor.type.getFunctionalInterfaceType(this, psi)
}
}
+22 -1
View File
@@ -1,3 +1,8 @@
import java.lang.Thread
import java.lang.Runnable
import java.util.concurrent.Callable
import java.util.function.Supplier
val notSam = { /* Not SAM */ }
var foo: java.lang.Runnable = {/* Variable */}
@@ -10,4 +15,20 @@ fun bar(): java.lang.Runnable {
val baz = java.lang.Runnable { /* SAM */ }
fun runRunnable(r: java.lang.Runnable) = r()
fun runRunnable(r: java.lang.Runnable) = r()
fun test1() {
val thread1 = Thread({ println("hello1") })
}
fun test2() {
val thread2 = Thread(Runnable { println("hello2") })
}
fun test3() {
ambiguousSamAcceptor(Supplier { "Supplier" })
ambiguousSamAcceptor(Callable { "Callable" })
}
fun ambiguousSamAcceptor(s: Supplier<String>): String = TODO()
fun ambiguousSamAcceptor(s: Callable<String>): String = TODO()
+58 -21
View File
@@ -2,7 +2,9 @@ package org.jetbrains.uast.test.kotlin
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiModifier
import com.intellij.testFramework.RunAll
import com.intellij.testFramework.UsefulTestCase
import com.intellij.util.ThrowableRunnable
import org.jetbrains.kotlin.asJava.toLightAnnotation
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -81,28 +83,61 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
}
}
@Test fun testSAM() {
@Test
fun testSAM() {
doTest("SAM") { _, file ->
assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Argument */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Return */}").functionalInterfaceType?.canonicalText)
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ /* SAM */ }").functionalInterfaceType?.canonicalText
runAll(
{ assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType) }, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Argument */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Return */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ /* SAM */ }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ println(\"hello1\") }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ println(\"hello2\") }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.util.function.Supplier<T>",
file.findElementByText<ULambdaExpression>("{ \"Supplier\" }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.util.concurrent.Callable<V>",
file.findElementByText<ULambdaExpression>("{ \"Callable\" }").functionalInterfaceType?.canonicalText
)
}
)
}
}
@@ -529,3 +564,5 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
fun <T, R> Iterable<T>.assertedFind(value: R, transform: (T) -> R): T =
find { transform(it) == value } ?: throw AssertionError("'$value' not found, only ${this.joinToString { transform(it).toString() }}")
fun runAll(vararg asserts: () -> Unit) = RunAll(*asserts.map { ThrowableRunnable<Throwable>(it) }.toTypedArray()).run()
@@ -2,7 +2,9 @@ package org.jetbrains.uast.test.kotlin
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiModifier
import com.intellij.testFramework.RunAll
import com.intellij.testFramework.UsefulTestCase
import com.intellij.util.ThrowableRunnable
import org.jetbrains.kotlin.asJava.toLightAnnotation
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -82,28 +84,61 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
}
}
@Test fun testSAM() {
@Test
fun testSAM() {
doTest("SAM") { _, file ->
assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Argument */}").functionalInterfaceType?.canonicalText)
assertEquals("java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Return */}").functionalInterfaceType?.canonicalText)
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ /* SAM */ }").functionalInterfaceType?.canonicalText
runAll(
{ assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType) }, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Argument */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{/* Return */}").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ /* SAM */ }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ println(\"hello1\") }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.lang.Runnable",
file.findElementByText<ULambdaExpression>("{ println(\"hello2\") }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.util.function.Supplier<T>",
file.findElementByText<ULambdaExpression>("{ \"Supplier\" }").functionalInterfaceType?.canonicalText
)
}, {
assertEquals(
"java.util.concurrent.Callable<V>",
file.findElementByText<ULambdaExpression>("{ \"Callable\" }").functionalInterfaceType?.canonicalText
)
}
)
}
}
@@ -545,3 +580,5 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
fun <T, R> Iterable<T>.assertedFind(value: R, transform: (T) -> R): T =
find { transform(it) == value } ?: throw AssertionError("'$value' not found, only ${this.joinToString { transform(it).toString() }}")
fun runAll(vararg asserts: () -> Unit) = RunAll(*asserts.map { ThrowableRunnable<Throwable>(it) }.toTypedArray()).run()