From ab36e20b1b4ca19aa622e26cd125de4b2e2ee5b4 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Tue, 31 Mar 2020 23:06:30 +0300 Subject: [PATCH] Uast: resolving local calls (KT-36275) --- .../uast/kotlin/KotlinUastLanguagePlugin.kt | 31 +++++++--- .../uast/kotlin/declarations/KotlinUClass.kt | 19 ++++-- .../internal/kotlinInternalUastUtils.kt | 21 ++++++- .../uast/kotlin/psi/UastFakeLightMethod.kt | 37 +++++++----- .../testData/LocalDeclarations.resolved.txt | 8 +-- ...AbstractKotlinResolveEverythingTest.kt.191 | 60 +++++++++++++++++++ .../uast-kotlin/tests/KotlinUastApiTest.kt | 38 +++++++++++- .../tests/KotlinUastResolveApiTest.kt | 2 +- 8 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 plugins/uast-kotlin/tests/AbstractKotlinResolveEverythingTest.kt.191 diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt index 21538d95991..8ecd0cd3fc4 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt @@ -39,7 +39,6 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall @@ -52,10 +51,7 @@ import org.jetbrains.uast.kotlin.declarations.KotlinUIdentifier import org.jetbrains.uast.kotlin.declarations.KotlinUMethod import org.jetbrains.uast.kotlin.declarations.KotlinUMethodWithFakeLightDelegate import org.jetbrains.uast.kotlin.expressions.* -import org.jetbrains.uast.kotlin.psi.UastFakeLightMethod -import org.jetbrains.uast.kotlin.psi.UastKotlinPsiParameter -import org.jetbrains.uast.kotlin.psi.UastKotlinPsiParameterBase -import org.jetbrains.uast.kotlin.psi.UastKotlinPsiVariable +import org.jetbrains.uast.kotlin.psi.* interface KotlinUastResolveProviderService { fun getBindingContext(element: KtElement): BindingContext @@ -167,6 +163,8 @@ class KotlinUastLanguagePlugin : UastLanguagePlugin { KotlinConverter.convertNonLocalProperty(element, null, requiredTypes) as Sequence element is KtParameter -> KotlinConverter.convertParameter(element, null, requiredTypes) as Sequence element is KtClassOrObject -> KotlinConverter.convertClassOrObject(element, null, requiredTypes) as Sequence + element is UastFakeLightPrimaryConstructor -> + KotlinConverter.convertFakeLightConstructorAlternatices(element, null, requiredTypes) as Sequence else -> sequenceOf(convertElementWithParent(element, requiredTypes.nonEmptyOr(DEFAULT_TYPES_LIST)) as? T).filterNotNull() } } @@ -507,7 +505,15 @@ internal object KotlinConverter { return with(expectedTypes) { when (original) { is KtLightMethod -> el(build(KotlinUMethod.Companion::create)) // .Companion is needed because of KT-13934 - is UastFakeLightMethod -> el { KotlinUMethodWithFakeLightDelegate(original.original, original, givenParent) } + is UastFakeLightMethod -> el { + val ktFunction = original.original + if (ktFunction.isLocal) + convertDeclaration(ktFunction, givenParent, expectedTypes) + else + KotlinUMethodWithFakeLightDelegate(ktFunction, original, givenParent) + } + is UastFakeLightPrimaryConstructor -> + convertFakeLightConstructorAlternatices(original, givenParent, expectedTypes).firstOrNull() is KtLightClass -> when (original.kotlinOrigin) { is KtEnumEntry -> el { convertEnumEntry(original.kotlinOrigin as KtEnumEntry, givenParent) @@ -586,9 +592,20 @@ internal object KotlinConverter { } } + internal fun convertFakeLightConstructorAlternatices( + original: UastFakeLightPrimaryConstructor, + givenParent: UElement?, + expectedTypes: Array> + ): Sequence { + return expectedTypes.accommodate( + alternative { convertDeclaration(original.original, givenParent, expectedTypes) as? UClass }, + alternative { KotlinConstructorUMethod(original.original, original, original.original, givenParent) } + ) + } + private fun getLightClassForFakeMethod(original: KtFunction): KtLightClass? { if (original.isLocal) return null - return (original.containingClassOrObject?.toLightClass() ?: original.containingKtFile.findFacadeClass()) + return getContainingLightClass(original) } fun convertDeclarationOrElement( diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt index cd927c974cc..bd777849a86 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt @@ -200,15 +200,22 @@ open class KotlinUClass private constructor( open class KotlinConstructorUMethod( private val ktClass: KtClassOrObject?, - override val psi: KtLightMethod, + override val psi: PsiMethod, + kotlinOrigin: KtDeclaration?, givenParent: UElement? -) : KotlinUMethod(psi, psi.kotlinOrigin, givenParent) { +) : KotlinUMethod(psi, kotlinOrigin, givenParent) { + + constructor( + ktClass: KtClassOrObject?, + psi: KtLightMethod, + givenParent: UElement? + ):this(ktClass, psi, psi.kotlinOrigin, givenParent) val isPrimary: Boolean - get() = psi.kotlinOrigin.let { it is KtPrimaryConstructor || it is KtClassOrObject } + get() = sourcePsi.let { it is KtPrimaryConstructor || it is KtClassOrObject } override val uastBody: UExpression? by lz { - val delegationCall: KtCallElement? = psi.kotlinOrigin.let { + val delegationCall: KtCallElement? = sourcePsi.let { when { isPrimary -> ktClass?.superTypeListEntries?.firstIsInstanceOrNull() it is KtSecondaryConstructor -> it.getDelegationCall() @@ -232,7 +239,7 @@ open class KotlinConstructorUMethod( override val uastAnchor: KotlinUIdentifier by lazy { KotlinUIdentifier( psi.nameIdentifier, - if (isPrimary) ktClass?.nameIdentifier else (psi.kotlinOrigin as? KtSecondaryConstructor)?.getConstructorKeyword(), + if (isPrimary) ktClass?.nameIdentifier else (sourcePsi as? KtSecondaryConstructor)?.getConstructorKeyword(), this ) } @@ -241,7 +248,7 @@ open class KotlinConstructorUMethod( open protected fun getBodyExpressions(): List { if (isPrimary) return getInitializers() - val bodyExpression = (psi.kotlinOrigin as? KtFunction)?.bodyExpression ?: return emptyList() + val bodyExpression = (sourcePsi as? KtFunction)?.bodyExpression ?: return emptyList() if (bodyExpression is KtBlockExpression) return bodyExpression.statements return listOf(bodyExpression) } 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 fadc9497882..07fc53c2f81 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 @@ -27,7 +27,9 @@ import com.intellij.psi.impl.compiled.SignatureParsing import com.intellij.psi.impl.compiled.StubBuildingVisitor import com.intellij.psi.util.PsiTypesUtil import org.jetbrains.kotlin.asJava.LightClassUtil +import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass +import org.jetbrains.kotlin.asJava.findFacadeClass import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.asJava.toLightElements import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype @@ -45,6 +47,7 @@ import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf 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.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils @@ -69,6 +72,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.uast.* import org.jetbrains.uast.kotlin.expressions.KotlinLocalFunctionUVariable +import org.jetbrains.uast.kotlin.psi.UastFakeLightMethod +import org.jetbrains.uast.kotlin.psi.UastFakeLightPrimaryConstructor import java.lang.ref.WeakReference import java.text.StringCharacterIterator @@ -97,17 +102,29 @@ internal fun resolveSource(context: KtElement, descriptor: DeclarationDescriptor && source is KtClassOrObject && source.primaryConstructor == null && source.secondaryConstructors.isEmpty() ) { - return source.toLightClass()?.constructors?.firstOrNull() + val lightClass = source.toLightClass() ?: return null + lightClass.constructors.firstOrNull()?.let { return it } + if (source.isLocal) { + return UastFakeLightPrimaryConstructor(source, lightClass) + } + return null } return when (source) { - is KtFunction -> LightClassUtil.getLightClassMethod(source) + is KtFunction -> + if (source.isLocal) + getContainingLightClass(source)?.let { UastFakeLightMethod(source, it) } + else + LightClassUtil.getLightClassMethod(source) is PsiMethod -> source null -> resolveDeserialized(context, descriptor) as? PsiMethod else -> null } } +internal fun getContainingLightClass(original: KtDeclaration): KtLightClass? = + (original.containingClassOrObject?.toLightClass() ?: original.containingKtFile.findFacadeClass()) + internal fun resolveContainingDeserializedClass(context: KtElement, memberDescriptor: DeserializedCallableMemberDescriptor): PsiClass? { val containingDeclaration = memberDescriptor.containingDeclaration return when (containingDeclaration) { diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/psi/UastFakeLightMethod.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/psi/UastFakeLightMethod.kt index 2174b3f8fb8..6294a97a6cb 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/psi/UastFakeLightMethod.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/psi/UastFakeLightMethod.kt @@ -12,8 +12,7 @@ import com.intellij.psi.impl.light.LightParameterListBuilder import com.intellij.psi.impl.light.LightTypeParameterBuilder import org.jetbrains.kotlin.asJava.elements.KotlinLightTypeParameterListBuilder import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.psi.KtFunction -import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.uast.UastErrorType @@ -21,18 +20,7 @@ import org.jetbrains.uast.kotlin.analyze import org.jetbrains.uast.kotlin.getType import org.jetbrains.uast.kotlin.toPsiType -internal class UastFakeLightMethod(internal val original: KtFunction, containingClass: PsiClass) : LightMethodBuilder( - original.manager, original.language, original.name ?: "", - LightParameterListBuilder(original.manager, original.language), - LightModifierList(original.manager) -) { - - init { - this.containingClass = containingClass - if (original.safeAs()?.isTopLevel == true) { - addModifier(PsiModifier.STATIC) - } - } +internal class UastFakeLightMethod(original: KtFunction, containingClass: PsiClass) : UastFakeLightMethodBase(original, containingClass) { private val _buildTypeParameterList by lazy { KotlinLightTypeParameterListBuilder(this).also { paramList -> @@ -86,6 +74,25 @@ internal class UastFakeLightMethod(internal val original: KtFunction, containing } override fun getParameterList(): PsiParameterList = paramsList +} + +internal class UastFakeLightPrimaryConstructor(original: KtClassOrObject, lightClass: PsiClass) : UastFakeLightMethodBase(original, lightClass){ + override fun isConstructor(): Boolean = true +} + +internal abstract class UastFakeLightMethodBase(internal val original: T, containingClass: PsiClass) : LightMethodBuilder( + original.manager, original.language, original.name ?: "", + LightParameterListBuilder(original.manager, original.language), + LightModifierList(original.manager) +) { + + init { + this.containingClass = containingClass + if (original.safeAs()?.isTopLevel == true) { + addModifier(PsiModifier.STATIC) + } + } + override fun getReturnType(): PsiType? { val context = original.analyze() @@ -99,7 +106,7 @@ internal class UastFakeLightMethod(internal val original: KtFunction, containing if (this === other) return true if (javaClass != other?.javaClass) return false - other as UastFakeLightMethod + other as UastFakeLightMethodBase<*> if (original != other.original) return false diff --git a/plugins/uast-kotlin/testData/LocalDeclarations.resolved.txt b/plugins/uast-kotlin/testData/LocalDeclarations.resolved.txt index ebf63090816..169ba294d2f 100644 --- a/plugins/uast-kotlin/testData/LocalDeclarations.resolved.txt +++ b/plugins/uast-kotlin/testData/LocalDeclarations.resolved.txt @@ -1,8 +1,8 @@ UTypeReferenceExpression (name = boolean) -> USimpleNameReferenceExpression (identifier = Boolean) -> PsiClass:Boolean: Boolean - UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))(resolves to null) -> USimpleNameReferenceExpression (identifier = Local) -> KtLightClassForLocalDeclaration:class Local: Local - UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))(resolves to null) -> USimpleNameReferenceExpression (identifier = Local) -> KtLightClassForLocalDeclaration:class Local: Local + UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))(resolves to LightMethodBuilder:Local) -> USimpleNameReferenceExpression (identifier = Local) -> KtLightClassForLocalDeclaration:class Local: Local + UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))(resolves to LightMethodBuilder:Local) -> USimpleNameReferenceExpression (identifier = Local) -> KtLightClassForLocalDeclaration:class Local: Local UTypeReferenceExpression (name = int) -> USimpleNameReferenceExpression (identifier = Int) -> PsiClass:Integer: Integer UTypeReferenceExpression (name = java.lang.String) -> USimpleNameReferenceExpression (identifier = String) -> PsiClass:String: String - UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0))(resolves to null) -> USimpleNameReferenceExpression (identifier = bar) -> LightVariableBuilder:bar: bar + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0))(resolves to LightMethodBuilder:bar) -> USimpleNameReferenceExpression (identifier = bar) -> LightVariableBuilder:bar: bar UBinaryExpression (operator = ==) -> USimpleNameReferenceExpression (identifier = ==) -> null: null - UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))(resolves to null) -> USimpleNameReferenceExpression (identifier = Local) -> KtLightClassForLocalDeclaration:class Local: Local + UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))(resolves to LightMethodBuilder:Local) -> USimpleNameReferenceExpression (identifier = Local) -> KtLightClassForLocalDeclaration:class Local: Local diff --git a/plugins/uast-kotlin/tests/AbstractKotlinResolveEverythingTest.kt.191 b/plugins/uast-kotlin/tests/AbstractKotlinResolveEverythingTest.kt.191 new file mode 100644 index 00000000000..4c0c166551f --- /dev/null +++ b/plugins/uast-kotlin/tests/AbstractKotlinResolveEverythingTest.kt.191 @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.uast.test.kotlin + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.uast.* +import org.jetbrains.uast.test.common.kotlin.IndentedPrintingVisitor +import org.jetbrains.uast.test.common.kotlin.visitUFileAndGetResult +import org.jetbrains.uast.test.env.kotlin.assertEqualsToFile +import java.io.File + + +abstract class AbstractKotlinResolveEverythingTest : AbstractKotlinUastTest() { + + private fun getTestFile(testName: String, ext: String) = + File(File(TEST_KOTLIN_MODEL_DIR, testName).canonicalPath + '.' + ext) + + + private fun UFile.resolvableWithTargets() = object : IndentedPrintingVisitor(KtBlockExpression::class) { + override fun render(element: PsiElement) = + sequenceOf(element.toUElementOfType(), element.toUElementOfType()).filterNotNull() + .filter { + when (it) { + is UCallExpression -> it.sourcePsi.safeAs()?.calleeExpression !is KtSimpleNameExpression + else -> true + } + }.takeIf { it.any() } + ?.joinTo(StringBuilder(), "\n") { ref -> + StringBuilder().apply { + val parent = ref.uastParent + append(parent?.asLogString()) + if (parent is UCallExpression) { + append("(resolves to ${parent.resolve()})") + } + append(" -> ") + append(ref.asLogString()) + append(" -> ") + append(ref.cast().resolve()) + append(": ") + append( + when (ref) { + is UReferenceExpression -> ref.resolvedName + is UCallExpression -> "" + else -> "" + } + ) + } + } + }.visitUFileAndGetResult(this) + + override fun check(testName: String, file: UFile) { + assertEqualsToFile("resolved", getTestFile(testName, "resolved.txt"), file.resolvableWithTargets()) + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt index 35782b652e2..c82193b0fa4 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt @@ -568,11 +568,43 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { "bar", localFunction.methodName ) - assertNull(localFunction.resolve()) + val localFunctionResolved = localFunction.resolve() + assertNotNull(localFunctionResolved) val receiver = localFunction.receiver ?: kfail("receiver expected") assertEquals("UReferenceExpression", receiver.asLogString()) - val uParameter = (receiver as UReferenceExpression).resolve().toUElement() ?: kfail("uelement expected") - assertEquals("ULambdaExpression", uParameter.asLogString()) + val uVariable = (receiver as UReferenceExpression).resolve().toUElement() ?: kfail("uelement expected") + assertEquals("ULambdaExpression", uVariable.asLogString()) + assertEquals(uVariable, localFunctionResolved.toUElement()) + } + } + + @Test + fun testLocalConstructorCall() { + doTest("LocalDeclarations") { _, file -> + val localFunction = file.findElementByTextFromPsi("bar() == Local()"). + findElementByText("Local()") + assertEquals( + "UIdentifier (Identifier (Local))", + localFunction.methodIdentifier?.asLogString() + ) + assertEquals( + "Local", + localFunction.methodIdentifier?.name + ) + assertEquals( + "", + localFunction.methodName + ) + val localFunctionResolved = localFunction.resolve() + assertNotNull(localFunctionResolved) + val classReference = localFunction.classReference ?: kfail("classReference expected") + assertEquals("USimpleNameReferenceExpression (identifier = , resolvesTo = Local)", classReference.asLogString()) + val localClass = classReference.resolve().toUElement() ?: kfail("uelement expected") + assertEquals("UClass (name = Local)", localClass.asLogString()) + assertEquals(localClass, localFunctionResolved.toUElement()) + val localPrimaryConstructor = localFunctionResolved.toUElementOfType() ?: kfail("constructor expected") + assertTrue(localPrimaryConstructor.isConstructor) + assertEquals(localClass, localPrimaryConstructor.uastParent) } } diff --git a/plugins/uast-kotlin/tests/KotlinUastResolveApiTest.kt b/plugins/uast-kotlin/tests/KotlinUastResolveApiTest.kt index 34e5d9ba326..55adb32f051 100644 --- a/plugins/uast-kotlin/tests/KotlinUastResolveApiTest.kt +++ b/plugins/uast-kotlin/tests/KotlinUastResolveApiTest.kt @@ -298,7 +298,7 @@ class KotlinUastResolveApiTest : KotlinLightCodeInsightFixtureTestCase() { ) val uCallExpression = myFixture.file.findElementAt(myFixture.caretOffset).toUElement().getUCallExpression().orFail("cant convert to UCallExpression") - val resolved = uCallExpression.resolve().orFail("cant resolve from $uCallExpression") as PsiVariable + val resolved = uCallExpression.resolve().orFail("cant resolve from $uCallExpression") TestCase.assertEquals("bar", resolved.name) }