From d7584df01bb7280a9ea311148cd90c8acf09c796 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Mon, 18 Feb 2019 17:24:40 +0300 Subject: [PATCH] Uast: making `ULambdaExpression.functionalInterfaceType` return non-null values for explicitly typed SAM-s with Java interfaces (KT-28272) --- .../internal/kotlinInternalUastUtils.kt | 15 ++-- plugins/uast-kotlin/testData/SAM.kt | 23 +++++- .../uast-kotlin/tests/KotlinUastApiTest.kt | 79 ++++++++++++++----- .../tests/KotlinUastApiTest.kt.191 | 79 ++++++++++++++----- 4 files changed, 148 insertions(+), 48 deletions(-) diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt index 2a6d3ed1af8..41b7e4616cd 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt @@ -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() ?: 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) } } diff --git a/plugins/uast-kotlin/testData/SAM.kt b/plugins/uast-kotlin/testData/SAM.kt index e390962292a..f742415efaf 100644 --- a/plugins/uast-kotlin/testData/SAM.kt +++ b/plugins/uast-kotlin/testData/SAM.kt @@ -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() \ No newline at end of file +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 = TODO() +fun ambiguousSamAcceptor(s: Callable): String = TODO() \ No newline at end of file diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt index 9b5413467d4..9d8e0255bf6 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt @@ -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("{ /* Not SAM */ }").functionalInterfaceType) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Variable */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Assignment */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Type Cast */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Argument */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Return */}").functionalInterfaceType?.canonicalText) - - assertEquals( - "java.lang.Runnable", - file.findElementByText("{ /* SAM */ }").functionalInterfaceType?.canonicalText + runAll( + { assertNull(file.findElementByText("{ /* Not SAM */ }").functionalInterfaceType) }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Variable */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Assignment */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Type Cast */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Argument */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Return */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{ /* SAM */ }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{ println(\"hello1\") }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{ println(\"hello2\") }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.util.function.Supplier", + file.findElementByText("{ \"Supplier\" }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.util.concurrent.Callable", + file.findElementByText("{ \"Callable\" }").functionalInterfaceType?.canonicalText + ) + } ) } } @@ -529,3 +564,5 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { fun Iterable.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(it) }.toTypedArray()).run() \ No newline at end of file diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.191 b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.191 index ee115572f34..f9e87a5a35c 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.191 +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.191 @@ -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("{ /* Not SAM */ }").functionalInterfaceType) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Variable */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Assignment */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Type Cast */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Argument */}").functionalInterfaceType?.canonicalText) - - assertEquals("java.lang.Runnable", - file.findElementByText("{/* Return */}").functionalInterfaceType?.canonicalText) - - assertEquals( - "java.lang.Runnable", - file.findElementByText("{ /* SAM */ }").functionalInterfaceType?.canonicalText + runAll( + { assertNull(file.findElementByText("{ /* Not SAM */ }").functionalInterfaceType) }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Variable */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Assignment */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Type Cast */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Argument */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{/* Return */}").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{ /* SAM */ }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{ println(\"hello1\") }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.lang.Runnable", + file.findElementByText("{ println(\"hello2\") }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.util.function.Supplier", + file.findElementByText("{ \"Supplier\" }").functionalInterfaceType?.canonicalText + ) + }, { + assertEquals( + "java.util.concurrent.Callable", + file.findElementByText("{ \"Callable\" }").functionalInterfaceType?.canonicalText + ) + } ) } } @@ -545,3 +580,5 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { fun Iterable.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(it) }.toTypedArray()).run() \ No newline at end of file