From a616f6cca999c48af0db9fd175ab26ce0418c316 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Tue, 17 Jul 2018 11:36:33 +0300 Subject: [PATCH] Uast: implicit receiver for local function calls (KT-25524) --- .../KotlinUFunctionCallExpression.kt | 9 +++- .../KotlinUFunctionCallExpression.kt.173 | 16 ++++--- plugins/uast-kotlin/testData/Lambdas.kt | 4 ++ plugins/uast-kotlin/testData/Lambdas.log.txt | 10 ++++ .../uast-kotlin/testData/Lambdas.render.txt | 6 ++- .../uast-kotlin/tests/KotlinUastApiTest.kt | 48 +++++++++++++++++++ .../tests/KotlinUastApiTest.kt.172 | 48 +++++++++++++++++++ .../tests/KotlinUastApiTest.kt.173 | 48 +++++++++++++++++++ 8 files changed, 179 insertions(+), 10 deletions(-) diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt index e48f2d6dede..ed3267c7825 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt @@ -22,6 +22,8 @@ import com.intellij.psi.PsiNamedElement import com.intellij.psi.PsiType import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ConstructorDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils @@ -110,12 +112,15 @@ class KotlinUFunctionCallExpression( } val ktNameReferenceExpression = psi.calleeExpression as? KtNameReferenceExpression ?: return null - val variableAsFunctionResolvedCall = resolvedCall as? VariableAsFunctionResolvedCall ?: return null + val variableCallDescriptor = + (resolvedCall as? VariableAsFunctionResolvedCall)?.variableCall?.resultingDescriptor + ?: (resolvedCall?.resultingDescriptor as? FunctionDescriptor)?.takeIf { it.visibility == Visibilities.LOCAL } + ?: return null // an implicit receiver for variables calls (KT-25524) return object : KotlinAbstractUExpression(this), UReferenceExpression { - private val resolvedDeclaration = variableAsFunctionResolvedCall.variableCall.resultingDescriptor.toSource() + private val resolvedDeclaration = variableCallDescriptor.toSource() override val psi: KtNameReferenceExpression get() = ktNameReferenceExpression diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.173 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.173 index 6f17e3040bc..8cbfe870b4d 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.173 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.173 @@ -18,15 +18,14 @@ package org.jetbrains.uast.kotlin import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiNamedElement import com.intellij.psi.PsiType import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.descriptors.ConstructorDescriptor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.psi.KtAnnotationEntry -import org.jetbrains.kotlin.psi.KtCallElement -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall @@ -94,12 +93,15 @@ class KotlinUFunctionCallExpression( } val ktNameReferenceExpression = psi.calleeExpression as? KtNameReferenceExpression ?: return null - val variableAsFunctionResolvedCall = resolvedCall as? VariableAsFunctionResolvedCall ?: return null + val variableCallDescriptor = + (resolvedCall as? VariableAsFunctionResolvedCall)?.variableCall?.resultingDescriptor + ?: (resolvedCall?.resultingDescriptor as? FunctionDescriptor)?.takeIf { it.visibility == Visibilities.LOCAL } + ?: return null // an implicit receiver for variables calls (KT-25524) return object : KotlinAbstractUExpression(this), UReferenceExpression { - private val resolvedDeclaration = variableAsFunctionResolvedCall.variableCall.resultingDescriptor.toSource() + private val resolvedDeclaration = variableCallDescriptor.toSource() override val psi: KtNameReferenceExpression get() = ktNameReferenceExpression diff --git a/plugins/uast-kotlin/testData/Lambdas.kt b/plugins/uast-kotlin/testData/Lambdas.kt index b4425aa7611..cac23a6536a 100644 --- a/plugins/uast-kotlin/testData/Lambdas.kt +++ b/plugins/uast-kotlin/testData/Lambdas.kt @@ -6,4 +6,8 @@ fun foo() { fun doSelectItem(selectItemFunction: () -> Unit) { selectItemFunction() + val baz = fun() { + Local() + } + baz() } \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/Lambdas.log.txt b/plugins/uast-kotlin/testData/Lambdas.log.txt index 19ba4ec1ada..0c9cf9e905f 100644 --- a/plugins/uast-kotlin/testData/Lambdas.log.txt +++ b/plugins/uast-kotlin/testData/Lambdas.log.txt @@ -26,3 +26,13 @@ UFile (package = ) UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0)) UIdentifier (Identifier (selectItemFunction)) USimpleNameReferenceExpression (identifier = invoke) + UDeclarationsExpression + ULocalVariable (name = baz) + ULambdaExpression + UBlockExpression + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0)) + UIdentifier (Identifier (Local)) + USimpleNameReferenceExpression (identifier = ) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0)) + UIdentifier (Identifier (baz)) + USimpleNameReferenceExpression (identifier = invoke) diff --git a/plugins/uast-kotlin/testData/Lambdas.render.txt b/plugins/uast-kotlin/testData/Lambdas.render.txt index 47ac24d34a4..5a53e1579bd 100644 --- a/plugins/uast-kotlin/testData/Lambdas.render.txt +++ b/plugins/uast-kotlin/testData/Lambdas.render.txt @@ -2,11 +2,15 @@ import java.util.stream.Stream public final class LambdasKt { public static final fun foo() : void { - Stream.empty().filter({ + Stream.empty().filter({ it.isEmpty() }) } public static final fun doSelectItem(@org.jetbrains.annotations.NotNull selectItemFunction: kotlin.jvm.functions.Function0) : void { invoke() + var baz: kotlin.jvm.functions.Function0 = fun () { + () + } + invoke() } } diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt index 3762ea6048c..9fa78d5d702 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt @@ -407,6 +407,54 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { } } + @Test + fun testLocalLambdaCall() { + doTest("Lambdas") { _, file -> + val lambdaCall = file.findElementByTextFromPsi("baz()") + assertEquals( + "UIdentifier (Identifier (baz))", + lambdaCall.methodIdentifier?.asLogString() + ) + assertEquals( + "baz", + lambdaCall.methodIdentifier?.name + ) + assertEquals( + "invoke", + lambdaCall.methodName + ) + val receiver = lambdaCall.receiver ?: kotlin.test.fail("receiver expected") + assertEquals("UReferenceExpression", receiver.asLogString()) + val uParameter = (receiver as UReferenceExpression).resolve().toUElement() ?: kotlin.test.fail("uelement expected") + assertEquals("ULocalVariable (name = baz)", uParameter.asLogString()) + } + } + + @Test + fun testLocalDeclarationCall() { + doTest("LocalDeclarations") { _, file -> + val localFunction = file.findElementByTextFromPsi("bar() == Local()"). + findElementByText("bar()") + assertEquals( + "UIdentifier (Identifier (bar))", + localFunction.methodIdentifier?.asLogString() + ) + assertEquals( + "bar", + localFunction.methodIdentifier?.name + ) + assertEquals( + "bar", + localFunction.methodName + ) + assertNull(localFunction.resolve()) + val receiver = localFunction.receiver ?: kotlin.test.fail("receiver expected") + assertEquals("UReferenceExpression", receiver.asLogString()) + val uParameter = (receiver as UReferenceExpression).resolve().toUElement() ?: kotlin.test.fail("uelement expected") + assertEquals("ULambdaExpression", uParameter.asLogString()) + } + } + } fun Iterable.assertedFind(value: R, transform: (T) -> R): T = diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.172 b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.172 index 0211d97e4dc..58a5380e82f 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.172 +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.172 @@ -252,4 +252,52 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { } } + @Test + fun testLocalLambdaCall() { + doTest("Lambdas") { _, file -> + val lambdaCall = file.findElementByTextFromPsi("baz()") + assertEquals( + "UIdentifier (Identifier (baz))", + lambdaCall.methodIdentifier?.asLogString() + ) + assertEquals( + "baz", + lambdaCall.methodIdentifier?.name + ) + assertEquals( + "invoke", + lambdaCall.methodName + ) + val receiver = lambdaCall.receiver ?: kotlin.test.fail("receiver expected") + assertEquals("UReferenceExpression", receiver.asLogString()) + val uParameter = (receiver as UReferenceExpression).resolve().toUElement() ?: kotlin.test.fail("uelement expected") + assertEquals("ULocalVariable (name = baz)", uParameter.asLogString()) + } + } + + @Test + fun testLocalDeclarationCall() { + doTest("LocalDeclarations") { _, file -> + val localFunction = file.findElementByTextFromPsi("bar() == Local()"). + findElementByText("bar()") + assertEquals( + "UIdentifier (Identifier (bar))", + localFunction.methodIdentifier?.asLogString() + ) + assertEquals( + "bar", + localFunction.methodIdentifier?.name + ) + assertEquals( + "bar", + localFunction.methodName + ) + assertNull(localFunction.resolve()) + val receiver = localFunction.receiver ?: kotlin.test.fail("receiver expected") + assertEquals("UReferenceExpression", receiver.asLogString()) + val uParameter = (receiver as UReferenceExpression).resolve().toUElement() ?: kotlin.test.fail("uelement expected") + assertEquals("ULambdaExpression", uParameter.asLogString()) + } + } + } diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.173 b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.173 index 2978b0b2329..33d093dfcf4 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.173 +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.173 @@ -371,6 +371,54 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { } } + @Test + fun testLocalLambdaCall() { + doTest("Lambdas") { _, file -> + val lambdaCall = file.findElementByTextFromPsi("baz()") + assertEquals( + "UIdentifier (Identifier (baz))", + lambdaCall.methodIdentifier?.asLogString() + ) + assertEquals( + "baz", + lambdaCall.methodIdentifier?.name + ) + assertEquals( + "invoke", + lambdaCall.methodName + ) + val receiver = lambdaCall.receiver ?: kotlin.test.fail("receiver expected") + assertEquals("UReferenceExpression", receiver.asLogString()) + val uParameter = (receiver as UReferenceExpression).resolve().toUElement() ?: kotlin.test.fail("uelement expected") + assertEquals("ULocalVariable (name = baz)", uParameter.asLogString()) + } + } + + @Test + fun testLocalDeclarationCall() { + doTest("LocalDeclarations") { _, file -> + val localFunction = file.findElementByTextFromPsi("bar() == Local()"). + findElementByText("bar()") + assertEquals( + "UIdentifier (Identifier (bar))", + localFunction.methodIdentifier?.asLogString() + ) + assertEquals( + "bar", + localFunction.methodIdentifier?.name + ) + assertEquals( + "bar", + localFunction.methodName + ) + assertNull(localFunction.resolve()) + val receiver = localFunction.receiver ?: kotlin.test.fail("receiver expected") + assertEquals("UReferenceExpression", receiver.asLogString()) + val uParameter = (receiver as UReferenceExpression).resolve().toUElement() ?: kotlin.test.fail("uelement expected") + assertEquals("ULambdaExpression", uParameter.asLogString()) + } + } + } fun Iterable.assertedFind(value: R, transform: (T) -> R): T = find { transform(it) == value } ?: throw AssertionError("'$value' not found, only ${this.joinToString { transform(it).toString() }}")