Uast: making ULambdaExpression.functionalInterfaceType return non-null values for explicitly typed SAM-s with Java interfaces (KT-28272)
This commit is contained in:
+10
-5
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+22
-1
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user