From 3d1061ee3cb5a888e9d77e407c73070f461de52a Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Tue, 26 Nov 2019 19:06:40 +0300 Subject: [PATCH] Uast: `KotlinUMethodWithFakeLightDelegate` 183-related fixes --- .../kotlin/KotlinUastLanguagePlugin.kt.183 | 19 +- .../kotlin/declarations/KotlinUClass.kt.183 | 386 ++++++++++++++++++ .../uast/kotlin/declarations/KotlinUMethod.kt | 10 + .../kotlin/declarations/KotlinUMethod.kt.183 | 18 +- .../kotlin/declarations/KotlinUMethod.kt.191 | 10 + 5 files changed, 437 insertions(+), 6 deletions(-) create mode 100644 plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.183 diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.183 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.183 index 0358a2d0e1e..6001ac52faa 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.183 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.183 @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade import org.jetbrains.kotlin.asJava.elements.* +import org.jetbrains.kotlin.asJava.findFacadeClass import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.LanguageVersionSettings @@ -481,7 +482,7 @@ internal object KotlinConverter { el { val lightMethod = LightClassUtil.getLightClassMethod(original) if (lightMethod != null) - convertDeclaration(lightMethod, givenParent, expectedTypes) + convertDeclaration(lightMethod, givenParent, requiredType) else KotlinUMethodWithFakeLightDelegate(original, givenParent) } @@ -510,7 +511,7 @@ internal object KotlinConverter { KotlinUParameter(lightParameter, original, givenParent) } - is KtFile -> el { KotlinUFile(original) } + is KtFile -> convertKtFile(original, givenParent, requiredType) is FakeFileForLightClass -> el { KotlinUFile(original.navigationElement) } is KtAnnotationEntry -> el(build(::KotlinUAnnotation)) is KtCallExpression -> @@ -554,13 +555,25 @@ internal object KotlinConverter { val methods = LightClassUtil.getLightClassPropertyMethods(property) return methods.backingField?.let { backingField -> with(requiredType) { - el { KotlinUField(backingField, (backingField as? KtLightElement<*,*>)?.kotlinOrigin, givenParent) } + el { KotlinUField(backingField, (backingField as? KtLightElement<*, *>)?.kotlinOrigin, givenParent) } } } ?: methods.getter?.let { getter -> convertDeclaration(getter, givenParent, requiredType) } } + private fun convertKtFile( + element: KtFile, + givenParent: UElement?, + requiredType: Class? + ): UElement? { + if (requiredType?.isAssignableFrom(KotlinUClass::class.java) == true) + return element.findFacadeClass()?.let { KotlinUClass.create(it, givenParent) } + if (requiredType?.isAssignableFrom(KotlinUFile::class.java) != false) + return KotlinUFile(element) + return null + } + internal fun convertOrEmpty(expression: KtExpression?, parent: UElement?): UExpression { return expression?.let { convertExpression(it, parent, null) } ?: UastEmptyExpression } diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.183 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.183 new file mode 100644 index 00000000000..4650a6c37a2 --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.183 @@ -0,0 +1,386 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.uast.kotlin + +import com.intellij.psi.* +import com.intellij.psi.impl.light.LightPsiClassBuilder +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade +import org.jetbrains.kotlin.asJava.classes.KtLightClassForScript +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.kotlin.utils.addIfNotNull +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.uast.* +import org.jetbrains.uast.internal.acceptList +import org.jetbrains.uast.kotlin.declarations.KotlinUIdentifier +import org.jetbrains.uast.kotlin.declarations.KotlinUMethod +import org.jetbrains.uast.kotlin.declarations.UastLightIdentifier +import org.jetbrains.uast.kotlin.kinds.KotlinSpecialExpressionKinds +import org.jetbrains.uast.visitor.UastVisitor + +abstract class AbstractKotlinUClass(givenParent: UElement?) : KotlinAbstractUElement(givenParent), UClassTypeSpecific, UAnchorOwner, + JvmDeclarationUElementPlaceholder { + + override val uastDeclarations by lz { + mutableListOf().apply { + addAll(fields) + addAll(initializers) + addAll(methods) + addAll(innerClasses) + } + } + + open val ktClass: KtClassOrObject? get() = (psi as? KtLightClass)?.kotlinOrigin + + override val uastSuperTypes: List + get() = ktClass?.superTypeListEntries.orEmpty().mapNotNull { it.typeReference }.map { + LazyKotlinUTypeReferenceExpression(it, this) + } + + val delegateExpressions: List + get() = ktClass?.superTypeListEntries.orEmpty() + .filterIsInstance() + .map { KotlinSupertypeDelegationUExpression(it, this) } + + override fun accept(visitor: UastVisitor) { + if (visitor.visitClass(this)) return + delegateExpressions.acceptList(visitor) + annotations.acceptList(visitor) + uastDeclarations.acceptList(visitor) + visitor.afterVisitClass(this) + } + + override val annotations: List by lz { + (sourcePsi as? KtModifierListOwner)?.annotationEntries.orEmpty().map { KotlinUAnnotation(it, this) } + } + + override fun equals(other: Any?) = other is AbstractKotlinUClass && psi == other.psi + override fun hashCode() = psi.hashCode() + +} + +class KotlinSupertypeDelegationUExpression(override val sourcePsi: KtDelegatedSuperTypeEntry, givenParent: UElement?) : + KotlinAbstractUExpression(givenParent), UExpressionList { + + override val psi: PsiElement? get() = sourcePsi + + val typeReference: UTypeReferenceExpression? by lazy { + sourcePsi.typeReference?.let { KotlinUTypeReferenceExpression(it.toPsiType(this), it, this) } + } + + val delegateExpression: UExpression? by lazy { + sourcePsi.delegateExpression?.let { kotlinUastPlugin.convertElement(it, this, UExpression::class.java) as? UExpression } + } + + override val expressions: List + get() = listOfNotNull(typeReference, delegateExpression) + + override val kind: UastSpecialExpressionKind get() = KotlinSpecialExpressionKinds.SUPER_DELEGATION + +} + +open class KotlinUClass private constructor( + psi: KtLightClass, + givenParent: UElement? +) : AbstractKotlinUClass(givenParent), PsiClass by psi { + + final override val ktClass = psi.kotlinOrigin + + override val javaPsi: KtLightClass = psi + + override val sourcePsi: KtClassOrObject? = ktClass + + override val psi = unwrap(psi) + + override fun getSourceElement() = sourcePsi ?: this + + override fun getOriginalElement(): PsiElement? = super.getOriginalElement() + + override fun getNameIdentifier(): PsiIdentifier? = UastLightIdentifier(psi, ktClass) + + override fun getContainingFile(): PsiFile? = unwrapFakeFileForLightClass(psi.containingFile) + + override val uastAnchor by lazy { getIdentifierSourcePsi()?.let { KotlinUIdentifier(nameIdentifier, it, this) } } + + private fun getIdentifierSourcePsi(): PsiElement? { + ktClass?.nameIdentifier?.let { return it } + (ktClass as? KtObjectDeclaration)?.getObjectKeyword()?.let { return it } + return null + } + + override fun getInnerClasses(): Array { + // filter DefaultImpls to avoid processing same methods from original interface multiple times + // filter Enum entry classes to avoid duplication with PsiEnumConstant initializer class + return psi.innerClasses.filter { + it.name != JvmAbi.DEFAULT_IMPLS_CLASS_NAME && !it.isEnumEntryLightClass() + }.mapNotNull { + getLanguagePlugin().convertOpt(it, this) + }.toTypedArray() + } + + override fun getSuperClass(): UClass? = super.getSuperClass() + override fun getFields(): Array = super.getFields() + override fun getInitializers(): Array = super.getInitializers() + + override fun getMethods(): Array { + val hasPrimaryConstructor = ktClass?.hasPrimaryConstructor() ?: false + var secondaryConstructorsCount = 0 + + fun createUMethod(psiMethod: PsiMethod): UMethod { + return if (psiMethod is KtLightMethod && + psiMethod.isConstructor) { + if (!hasPrimaryConstructor && secondaryConstructorsCount++ == 0) + KotlinSecondaryConstructorWithInitializersUMethod(ktClass, psiMethod, this) + else + KotlinConstructorUMethod(ktClass, psiMethod, this) + } else { + getLanguagePlugin().convertOpt(psiMethod, this) ?: reportConvertFailure(psiMethod) + } + } + + fun isDelegatedMethod(psiMethod: PsiMethod) = psiMethod is KtLightMethod && psiMethod.isDelegated + + val result = ArrayList(javaPsi.methods.size) + val handledKtDeclarations = mutableSetOf() + + for (lightMethod in javaPsi.methods) { + if (isDelegatedMethod(lightMethod)) continue + val uMethod = createUMethod(lightMethod) + result.add(uMethod) + handledKtDeclarations.addIfNotNull(uMethod.sourcePsi) + } + + val ktDeclarations: List = run ktDeclarations@{ + ktClass?.let { return@ktDeclarations it.declarations } + (javaPsi as? KtLightClassForFacade)?.let { facade -> + return@ktDeclarations facade.files.flatMap { file -> file.declarations } + } + emptyList() + } + + ktDeclarations.asSequence() + .filterNot { handledKtDeclarations.contains(it) } + .mapNotNullTo(result) { KotlinConverter.convertDeclaration(it, this, null) as? UMethod } + + return result.toTypedArray() + } + + private fun PsiClass.isEnumEntryLightClass() = (this as? KtLightClass)?.kotlinOrigin is KtEnumEntry + + companion object { + fun create(psi: KtLightClass, containingElement: UElement?): UClass = when (psi) { + is PsiAnonymousClass -> KotlinUAnonymousClass(psi, containingElement) + is KtLightClassForScript -> KotlinScriptUClass(psi, containingElement) + else -> KotlinUClass(psi, containingElement) + } + } + +} + +open class KotlinConstructorUMethod( + private val ktClass: KtClassOrObject?, + override val psi: KtLightMethod, + givenParent: UElement? +) : KotlinUMethod(psi, psi.kotlinOrigin, givenParent) { + + val isPrimary: Boolean + get() = psi.kotlinOrigin.let { it is KtPrimaryConstructor || it is KtClassOrObject } + + override val uastBody: UExpression? by lz { + val delegationCall: KtCallElement? = psi.kotlinOrigin.let { + when { + isPrimary -> ktClass?.superTypeListEntries?.firstIsInstanceOrNull() + it is KtSecondaryConstructor -> it.getDelegationCall() + else -> null + } + } + val bodyExpressions = getBodyExpressions() + if (delegationCall == null && bodyExpressions.isEmpty()) return@lz null + KotlinUBlockExpression.KotlinLazyUBlockExpression(this) { uastParent -> + SmartList().apply { + delegationCall?.let { + add(KotlinUFunctionCallExpression(it, uastParent)) + } + bodyExpressions.forEach { + add(KotlinConverter.convertOrEmpty(it, uastParent)) + } + } + } + } + + override val uastAnchor: KotlinUIdentifier by lazy { + KotlinUIdentifier( + psi.nameIdentifier, + if (isPrimary) ktClass?.nameIdentifier else (psi.kotlinOrigin as? KtSecondaryConstructor)?.getConstructorKeyword(), + this + ) + } + + override val javaPsi = psi + + open protected fun getBodyExpressions(): List { + if (isPrimary) return getInitializers() + val bodyExpression = (psi.kotlinOrigin as? KtFunction)?.bodyExpression ?: return emptyList() + if (bodyExpression is KtBlockExpression) return bodyExpression.statements + return listOf(bodyExpression) + } + + protected fun getInitializers() = ktClass?.getAnonymousInitializers()?.mapNotNull { it.body } ?: emptyList() + +} + +// This class was created as a workaround for KT-21617 to be the only constructor which includes `init` block +// when there is no primary constructors in the class. +// It is expected to have only one constructor of this type in a UClass. +class KotlinSecondaryConstructorWithInitializersUMethod( + ktClass: KtClassOrObject?, + psi: KtLightMethod, + givenParent: UElement? +) : KotlinConstructorUMethod(ktClass, psi, givenParent) { + override fun getBodyExpressions(): List = getInitializers() + super.getBodyExpressions() +} + +class KotlinUAnonymousClass( + psi: PsiAnonymousClass, + givenParent: UElement? +) : AbstractKotlinUClass(givenParent), UAnonymousClass, PsiAnonymousClass by psi { + + override val psi: PsiAnonymousClass = unwrap(psi) + + override val javaPsi: PsiAnonymousClass = psi + + override val sourcePsi: KtClassOrObject? = ktClass + + override fun getOriginalElement(): PsiElement? = super.getOriginalElement() + + override fun getSuperClass(): UClass? = super.getSuperClass() + override fun getFields(): Array = super.getFields() + override fun getMethods(): Array = super.getMethods() + override fun getInitializers(): Array = super.getInitializers() + override fun getInnerClasses(): Array = super.getInnerClasses() + + override fun getContainingFile(): PsiFile = unwrapFakeFileForLightClass(psi.containingFile) + + override val uastAnchor by lazy { + val ktClassOrObject = (psi.originalElement as? KtLightClass)?.kotlinOrigin as? KtObjectDeclaration ?: return@lazy null + KotlinUIdentifier(ktClassOrObject.getObjectKeyword(), this) + } + +} + +class KotlinScriptUClass( + psi: KtLightClassForScript, + givenParent: UElement? +) : AbstractKotlinUClass(givenParent), PsiClass by psi { + override fun getContainingFile(): PsiFile = unwrapFakeFileForLightClass(psi.containingFile) + + override fun getNameIdentifier(): PsiIdentifier? = UastLightIdentifier(psi, psi.kotlinOrigin) + + override val uastAnchor by lazy { KotlinUIdentifier(nameIdentifier, sourcePsi?.nameIdentifier, this) } + + override val javaPsi: PsiClass = psi + + override val sourcePsi: KtClassOrObject? = psi.kotlinOrigin + + override val psi = unwrap(psi) + + override fun getSuperClass(): UClass? = super.getSuperClass() + + override fun getFields(): Array = super.getFields() + + override fun getInitializers(): Array = super.getInitializers() + + override fun getInnerClasses(): Array = + psi.innerClasses.mapNotNull { getLanguagePlugin().convertOpt(it, this) }.toTypedArray() + + override fun getMethods(): Array = psi.methods.map(this::createUMethod).toTypedArray() + + private fun createUMethod(method: PsiMethod): UMethod { + return if (method.isConstructor) { + KotlinScriptConstructorUMethod(psi.script, method as KtLightMethod, this) + } + else { + getLanguagePlugin().convertOpt(method, this) ?: reportConvertFailure(method) + } + } + + override fun getOriginalElement(): PsiElement? = psi.originalElement + + class KotlinScriptConstructorUMethod( + script: KtScript, + override val psi: KtLightMethod, + givenParent: UElement? + ) : KotlinUMethod(psi, psi.kotlinOrigin, givenParent) { + override val uastBody: UExpression? by lz { + val initializers = script.declarations.filterIsInstance() + KotlinUBlockExpression.create(initializers, this) + } + override val javaPsi = psi + } +} + +/** + * implementation of [UClass] for invalid code, when it is impossible to create a [KtLightClass] + */ +class KotlinInvalidUClass( + override val psi: PsiClass, + givenParent: UElement? +) : AbstractKotlinUClass(givenParent), PsiClass by psi { + + constructor(name: String, context: PsiElement, givenParent: UElement?) : this(LightPsiClassBuilder(context, name), givenParent) + + override fun getContainingFile(): PsiFile? = uastParent?.getContainingUFile()?.sourcePsi as? PsiFile + + override val sourcePsi: PsiElement? get() = null + + override val uastAnchor: UIdentifier? get() = null + + override val javaPsi: PsiClass get() = psi + + override fun getFields(): Array = emptyArray() + + override fun getInitializers(): Array = emptyArray() + + override fun getInnerClasses(): Array = emptyArray() + + override fun getMethods(): Array = emptyArray() + + override fun getSuperClass(): UClass? = null + + override fun getOriginalElement(): PsiElement? = null +} + +private fun reportConvertFailure(psiMethod: PsiMethod): Nothing { + val isValid = psiMethod.isValid + val report = KotlinExceptionWithAttachments( + "cant convert $psiMethod of ${psiMethod.javaClass} to UMethod" + + if (!isValid) " (method is not valid)" else "" + ) + + if (isValid) { + report.withAttachment("method", psiMethod.text) + psiMethod.containingFile?.let { + report.withAttachment("file", it.text) + } + } + + throw report +} \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt index a2e872840c1..fe461141d46 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt @@ -161,6 +161,16 @@ class KotlinUMethodWithFakeLightDelegate(val original: KtFunction, givenParent: KotlinUMethod(buildLightMethodFake(original), original, givenParent) { override val annotations: List get() = original.annotationEntries.mapNotNull { it.toUElementOfType() } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as KotlinUMethodWithFakeLightDelegate + if (original != other.original) return false + return true + } + + override fun hashCode(): Int = original.hashCode() } internal fun wrapExpressionBody(function: UElement, bodyExpression: KtExpression): UExpression? = when (bodyExpression) { diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.183 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.183 index 4b45f736aa7..b4e702c0f34 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.183 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.183 @@ -16,7 +16,9 @@ package org.jetbrains.uast.kotlin.declarations -import com.intellij.psi.* +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.impl.light.LightMethodBuilder import com.intellij.psi.impl.light.LightModifierList import com.intellij.psi.impl.light.LightParameterListBuilder @@ -61,8 +63,8 @@ open class KotlinUMethod( override val annotations: List by lz { psi.annotations - .mapNotNull { (it as? KtLightElement<*, *>)?.kotlinOrigin as? KtAnnotationEntry } - .map { KotlinUAnnotation(it, this) } + .mapNotNull { (it as? KtLightElement<*, *>)?.kotlinOrigin as? KtAnnotationEntry } + .map { KotlinUAnnotation(it, this) } } private val receiver by lz { (sourcePsi as? KtCallableDeclaration)?.receiverTypeReference } @@ -157,6 +159,16 @@ class KotlinUMethodWithFakeLightDelegate(val original: KtFunction, givenParent: KotlinUMethod(buildLightMethodFake(original), original, givenParent) { override val annotations: List get() = original.annotationEntries.mapNotNull { it.toUElementOfType() } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as KotlinUMethodWithFakeLightDelegate + if (original != other.original) return false + return true + } + + override fun hashCode(): Int = original.hashCode() } internal fun wrapExpressionBody(function: UElement, bodyExpression: KtExpression): UExpression? = when (bodyExpression) { diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.191 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.191 index 3e0ee3380cc..15a533f5130 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.191 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.191 @@ -162,6 +162,16 @@ class KotlinUMethodWithFakeLightDelegate(val original: KtFunction, givenParent: KotlinUMethod(buildLightMethodFake(original), original, givenParent) { override val annotations: List get() = original.annotationEntries.mapNotNull { it.toUElementOfType() } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as KotlinUMethodWithFakeLightDelegate + if (original != other.original) return false + return true + } + + override fun hashCode(): Int = original.hashCode() } internal fun wrapExpressionBody(function: UElement, bodyExpression: KtExpression): UExpression? = when (bodyExpression) {