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.builtins.isBuiltinFunctionalTypeOrSubtype
|
||||||
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
|
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
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.lazy.descriptors.LazyJavaPackageFragment
|
||||||
|
import org.jetbrains.kotlin.load.java.sam.SamAdapterDescriptor
|
||||||
import org.jetbrains.kotlin.load.java.sam.SamConstructorDescriptor
|
import org.jetbrains.kotlin.load.java.sam.SamConstructorDescriptor
|
||||||
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement
|
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement
|
||||||
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
|
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.JvmMemberSignature
|
||||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
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.type.MapPsiToAsmDesc
|
||||||
import org.jetbrains.kotlin.types.*
|
import org.jetbrains.kotlin.types.*
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isInterface
|
import org.jetbrains.kotlin.types.typeUtil.isInterface
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
import org.jetbrains.uast.*
|
import org.jetbrains.uast.*
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.text.StringCharacterIterator
|
import java.text.StringCharacterIterator
|
||||||
@@ -311,15 +315,16 @@ internal fun KotlinType.getFunctionalInterfaceType(source: UElement, element: Kt
|
|||||||
internal fun KotlinULambdaExpression.getFunctionalInterfaceType(): PsiType? {
|
internal fun KotlinULambdaExpression.getFunctionalInterfaceType(): PsiType? {
|
||||||
val parent = psi.parent
|
val parent = psi.parent
|
||||||
if (parent is KtBinaryExpressionWithTypeRHS) return parent.right?.getType()?.getFunctionalInterfaceType(this, psi)
|
if (parent is KtBinaryExpressionWithTypeRHS) return parent.right?.getType()?.getFunctionalInterfaceType(this, psi)
|
||||||
if (parent is KtLambdaArgument) run {
|
if (parent is KtValueArgument) run {
|
||||||
val callExpression = parent.parent as? KtCallExpression ?: return@run
|
val callExpression = parent.parents.take(2).firstIsInstanceOrNull<KtCallExpression>() ?: return@run
|
||||||
val resolvedCall = callExpression.getResolvedCall(callExpression.analyze()) ?: return@run
|
val resolvedCall = callExpression.getResolvedCall(callExpression.analyze()) ?: return@run
|
||||||
val candidateDescriptor = resolvedCall.candidateDescriptor
|
val candidateDescriptor = resolvedCall.candidateDescriptor as? SyntheticMemberDescriptor<*> ?: return@run
|
||||||
when (candidateDescriptor) {
|
when (candidateDescriptor) {
|
||||||
is SamConstructorDescriptor -> return candidateDescriptor.returnType?.getFunctionalInterfaceType(this, psi)
|
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 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)
|
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 */ }
|
val notSam = { /* Not SAM */ }
|
||||||
var foo: java.lang.Runnable = {/* Variable */}
|
var foo: java.lang.Runnable = {/* Variable */}
|
||||||
@@ -10,4 +15,20 @@ fun bar(): java.lang.Runnable {
|
|||||||
|
|
||||||
val baz = java.lang.Runnable { /* SAM */ }
|
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.PsiAnnotation
|
||||||
import com.intellij.psi.PsiModifier
|
import com.intellij.psi.PsiModifier
|
||||||
|
import com.intellij.testFramework.RunAll
|
||||||
import com.intellij.testFramework.UsefulTestCase
|
import com.intellij.testFramework.UsefulTestCase
|
||||||
|
import com.intellij.util.ThrowableRunnable
|
||||||
import org.jetbrains.kotlin.asJava.toLightAnnotation
|
import org.jetbrains.kotlin.asJava.toLightAnnotation
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
@@ -81,28 +83,61 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun testSAM() {
|
@Test
|
||||||
|
fun testSAM() {
|
||||||
doTest("SAM") { _, file ->
|
doTest("SAM") { _, file ->
|
||||||
assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType)
|
runAll(
|
||||||
|
{ assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType) }, {
|
||||||
assertEquals("java.lang.Runnable",
|
assertEquals(
|
||||||
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText)
|
"java.lang.Runnable",
|
||||||
|
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText
|
||||||
assertEquals("java.lang.Runnable",
|
)
|
||||||
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText)
|
}, {
|
||||||
|
assertEquals(
|
||||||
assertEquals("java.lang.Runnable",
|
"java.lang.Runnable",
|
||||||
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText)
|
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText
|
||||||
|
)
|
||||||
assertEquals("java.lang.Runnable",
|
}, {
|
||||||
file.findElementByText<ULambdaExpression>("{/* Argument */}").functionalInterfaceType?.canonicalText)
|
assertEquals(
|
||||||
|
"java.lang.Runnable",
|
||||||
assertEquals("java.lang.Runnable",
|
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText
|
||||||
file.findElementByText<ULambdaExpression>("{/* Return */}").functionalInterfaceType?.canonicalText)
|
)
|
||||||
|
}, {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"java.lang.Runnable",
|
"java.lang.Runnable",
|
||||||
file.findElementByText<ULambdaExpression>("{ /* SAM */ }").functionalInterfaceType?.canonicalText
|
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 =
|
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() }}")
|
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.PsiAnnotation
|
||||||
import com.intellij.psi.PsiModifier
|
import com.intellij.psi.PsiModifier
|
||||||
|
import com.intellij.testFramework.RunAll
|
||||||
import com.intellij.testFramework.UsefulTestCase
|
import com.intellij.testFramework.UsefulTestCase
|
||||||
|
import com.intellij.util.ThrowableRunnable
|
||||||
import org.jetbrains.kotlin.asJava.toLightAnnotation
|
import org.jetbrains.kotlin.asJava.toLightAnnotation
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
@@ -82,28 +84,61 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun testSAM() {
|
@Test
|
||||||
|
fun testSAM() {
|
||||||
doTest("SAM") { _, file ->
|
doTest("SAM") { _, file ->
|
||||||
assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType)
|
runAll(
|
||||||
|
{ assertNull(file.findElementByText<ULambdaExpression>("{ /* Not SAM */ }").functionalInterfaceType) }, {
|
||||||
assertEquals("java.lang.Runnable",
|
assertEquals(
|
||||||
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText)
|
"java.lang.Runnable",
|
||||||
|
file.findElementByText<ULambdaExpression>("{/* Variable */}").functionalInterfaceType?.canonicalText
|
||||||
assertEquals("java.lang.Runnable",
|
)
|
||||||
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText)
|
}, {
|
||||||
|
assertEquals(
|
||||||
assertEquals("java.lang.Runnable",
|
"java.lang.Runnable",
|
||||||
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText)
|
file.findElementByText<ULambdaExpression>("{/* Assignment */}").functionalInterfaceType?.canonicalText
|
||||||
|
)
|
||||||
assertEquals("java.lang.Runnable",
|
}, {
|
||||||
file.findElementByText<ULambdaExpression>("{/* Argument */}").functionalInterfaceType?.canonicalText)
|
assertEquals(
|
||||||
|
"java.lang.Runnable",
|
||||||
assertEquals("java.lang.Runnable",
|
file.findElementByText<ULambdaExpression>("{/* Type Cast */}").functionalInterfaceType?.canonicalText
|
||||||
file.findElementByText<ULambdaExpression>("{/* Return */}").functionalInterfaceType?.canonicalText)
|
)
|
||||||
|
}, {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"java.lang.Runnable",
|
"java.lang.Runnable",
|
||||||
file.findElementByText<ULambdaExpression>("{ /* SAM */ }").functionalInterfaceType?.canonicalText
|
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 =
|
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() }}")
|
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