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.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)
} }
} }
+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 */ } 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()
+58 -21
View File
@@ -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()