Uast: resolving local calls (KT-36275)

This commit is contained in:
Nicolay Mitropolsky
2020-03-31 23:06:30 +03:00
parent 6803b37d14
commit ab36e20b1b
8 changed files with 178 additions and 38 deletions
@@ -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<T>
element is KtParameter -> KotlinConverter.convertParameter(element, null, requiredTypes) as Sequence<T>
element is KtClassOrObject -> KotlinConverter.convertClassOrObject(element, null, requiredTypes) as Sequence<T>
element is UastFakeLightPrimaryConstructor ->
KotlinConverter.convertFakeLightConstructorAlternatices(element, null, requiredTypes) as Sequence<T>
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<UMethod>(build(KotlinUMethod.Companion::create)) // .Companion is needed because of KT-13934
is UastFakeLightMethod -> el<UMethod> { KotlinUMethodWithFakeLightDelegate(original.original, original, givenParent) }
is UastFakeLightMethod -> el<UMethod> {
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<UEnumConstant> {
convertEnumEntry(original.kotlinOrigin as KtEnumEntry, givenParent)
@@ -586,9 +592,20 @@ internal object KotlinConverter {
}
}
internal fun convertFakeLightConstructorAlternatices(
original: UastFakeLightPrimaryConstructor,
givenParent: UElement?,
expectedTypes: Array<out Class<out UElement>>
): Sequence<UElement> {
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(
@@ -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<KtSuperTypeCallEntry>()
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<KtExpression> {
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)
}
@@ -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) {
@@ -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 ?: "<no name provided>",
LightParameterListBuilder(original.manager, original.language),
LightModifierList(original.manager)
) {
init {
this.containingClass = containingClass
if (original.safeAs<KtNamedFunction>()?.isTopLevel == true) {
addModifier(PsiModifier.STATIC)
}
}
internal class UastFakeLightMethod(original: KtFunction, containingClass: PsiClass) : UastFakeLightMethodBase<KtFunction>(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<KtClassOrObject>(original, lightClass){
override fun isConstructor(): Boolean = true
}
internal abstract class UastFakeLightMethodBase<T: KtElement>(internal val original: T, containingClass: PsiClass) : LightMethodBuilder(
original.manager, original.language, original.name ?: "<no name provided>",
LightParameterListBuilder(original.manager, original.language),
LightModifierList(original.manager)
) {
init {
this.containingClass = containingClass
if (original.safeAs<KtNamedFunction>()?.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
@@ -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
@@ -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<UReferenceExpression>(), element.toUElementOfType<UCallExpression>()).filterNotNull()
.filter {
when (it) {
is UCallExpression -> it.sourcePsi.safeAs<KtCallElement>()?.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<UResolvable>().resolve())
append(": ")
append(
when (ref) {
is UReferenceExpression -> ref.resolvedName
is UCallExpression -> ""
else -> "<none>"
}
)
}
}
}.visitUFileAndGetResult(this)
override fun check(testName: String, file: UFile) {
assertEqualsToFile("resolved", getTestFile(testName, "resolved.txt"), file.resolvableWithTargets())
}
}
+35 -3
View File
@@ -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<UElement>("bar() == Local()").
findElementByText<UCallExpression>("Local()")
assertEquals(
"UIdentifier (Identifier (Local))",
localFunction.methodIdentifier?.asLogString()
)
assertEquals(
"Local",
localFunction.methodIdentifier?.name
)
assertEquals(
"<init>",
localFunction.methodName
)
val localFunctionResolved = localFunction.resolve()
assertNotNull(localFunctionResolved)
val classReference = localFunction.classReference ?: kfail("classReference expected")
assertEquals("USimpleNameReferenceExpression (identifier = <init>, 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<UMethod>() ?: kfail("constructor expected")
assertTrue(localPrimaryConstructor.isConstructor)
assertEquals(localClass, localPrimaryConstructor.uastParent)
}
}
@@ -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)
}