diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinAbstractUElement.kt.172 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinAbstractUElement.kt.172 new file mode 100644 index 00000000000..52b28bd93cb --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinAbstractUElement.kt.172 @@ -0,0 +1,211 @@ +/* + * 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.PsiElement +import org.jetbrains.kotlin.asJava.LightClassUtil +import org.jetbrains.kotlin.asJava.classes.KtLightClassForLocalDeclaration +import org.jetbrains.kotlin.asJava.toLightGetter +import org.jetbrains.kotlin.asJava.toLightSetter +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.uast.* +import org.jetbrains.uast.kotlin.expressions.KotlinUElvisExpression +import org.jetbrains.uast.kotlin.psi.UastKotlinPsiVariable + +abstract class KotlinAbstractUElement(private val givenParent: UElement?) : UElement, JvmDeclarationUElement { + + override val uastParent: UElement? by lz { + givenParent ?: convertParent() + } + + protected open fun convertParent(): UElement? { + val psi = psi + var parent = psi?.parent ?: psi?.containingFile + + if (psi is KtLightClassForLocalDeclaration) { + val originParent = psi.kotlinOrigin.parent + parent = when (originParent) { + is KtClassBody -> originParent.parent + else -> originParent + } + } + + if (psi is KtAnnotationEntry) { + val parentUnwrapped = KotlinConverter.unwrapElements(parent) ?: return null + val target = psi.useSiteTarget?.getAnnotationUseSiteTarget() + when (target) { + AnnotationUseSiteTarget.PROPERTY_GETTER -> + parent = (parentUnwrapped as? KtProperty)?.getter + ?: (parentUnwrapped as? KtParameter)?.toLightGetter() + ?: parent + + AnnotationUseSiteTarget.PROPERTY_SETTER -> + parent = (parentUnwrapped as? KtProperty)?.setter + ?: (parentUnwrapped as? KtParameter)?.toLightSetter() + ?: parent + AnnotationUseSiteTarget.FIELD -> + parent = (parentUnwrapped as? KtProperty) + ?: (parentUnwrapped as? KtParameter) + ?.takeIf { it.isPropertyParameter() } + ?.let(LightClassUtil::getLightClassBackingField) + ?: parent + AnnotationUseSiteTarget.SETTER_PARAMETER -> + parent = (parentUnwrapped as? KtParameter) + ?.toLightSetter()?.parameterList?.parameters?.firstOrNull() ?: parent + } + } + if (psi is UastKotlinPsiVariable && parent != null) { + parent = parent.parent + } + + while (parent is KtStringTemplateEntryWithExpression || + parent is KtStringTemplateExpression && parent.entries.size == 1) { + parent = parent.parent + } + + if (parent is KtWhenConditionWithExpression) { + parent = parent.parent + } + + if (parent is KtImportList) { + parent = parent.parent + } + + if (psi is KtFunctionLiteral && parent is KtLambdaExpression) { + parent = parent.parent + } + + if (parent is KtLambdaArgument) { + parent = parent.parent + } + + if (psi is KtSuperTypeCallEntry) { + parent = parent?.parent + } + + val result = doConvertParent(this, parent) + if (result == this) { + throw IllegalStateException("Loop in parent structure when converting a $psi of type ${psi?.javaClass} with parent $parent of type ${parent?.javaClass} text: [${parent?.text}]") + } + + return result + } + + override fun equals(other: Any?): Boolean { + if (other !is UElement) { + return false + } + + return this.psi == other.psi + } + + override fun hashCode() = psi?.hashCode() ?: 0 +} + +fun doConvertParent(element: UElement, parent: PsiElement?): UElement? { + val parentUnwrapped = KotlinConverter.unwrapElements(parent) ?: return null + if (parent is KtValueArgument && parentUnwrapped is KtAnnotationEntry) { + return (KotlinUastLanguagePlugin().convertElementWithParent(parentUnwrapped, null) as? KotlinUAnnotation) + ?.findAttributeValueExpression(parent) + } + + if (parent is KtParameter) { + val annotationClass = findAnnotationClassFromConstructorParameter(parent) + if (annotationClass != null) { + return annotationClass.methods.find { it.name == parent.name } + } + } + + if (parent is KtClassInitializer) { + val containingClass = parent.containingClassOrObject + if (containingClass != null) { + val containingUClass = KotlinUastLanguagePlugin().convertElementWithParent(containingClass, null) as? KotlinUClass + containingUClass?.methods?.filterIsInstance()?.firstOrNull { it.isPrimary }?.let { + return it.uastBody + } + } + } + + val result = KotlinUastLanguagePlugin().convertElementWithParent(parentUnwrapped, null) + + if (result is UEnumConstant && element is UDeclaration) { + return result.initializingClass + } + + if (result is USwitchClauseExpressionWithBody && !isInConditionBranch(element, result)) { + return result.body + } + + if (result is KotlinUDestructuringDeclarationExpression && + element.psi == (parent as KtDestructuringDeclaration).initializer) { + return result.tempVarAssignment + } + + if (result is KotlinUElvisExpression && parent is KtBinaryExpression) { + when (element.psi) { + parent.left -> return result.lhsDeclaration + parent.right -> return result.rhsIfExpression + } + } + + if (result is UMethod + && result !is KotlinConstructorUMethod // no sense to wrap super calls with `return` + && element is UExpression + && element !is UBlockExpression + && element !is UTypeReferenceExpression // when element is a type in extension methods + ) { + return KotlinUBlockExpression.KotlinLazyUBlockExpression(result, { block -> + listOf(KotlinUImplicitReturnExpression(block).apply { returnExpression = element }) + }).expressions.single() + } + + return result +} + +private fun isInConditionBranch(element: UElement, result: USwitchClauseExpressionWithBody) = + element.psi?.parentsWithSelf?.takeWhile { it !== result.psi }?.any { it is KtWhenCondition } ?: false + + +private fun findAnnotationClassFromConstructorParameter(parameter: KtParameter): UClass? { + val primaryConstructor = parameter.getStrictParentOfType() ?: return null + val containingClass = primaryConstructor.getContainingClassOrObject() + if (containingClass.isAnnotation()) { + return KotlinUastLanguagePlugin().convertElementWithParent(containingClass, null) as? UClass + } + return null +} + +abstract class KotlinAbstractUExpression(givenParent: UElement?) + : KotlinAbstractUElement(givenParent), UExpression, JvmDeclarationUElement { + + override val javaPsi: PsiElement? = null + + override val sourcePsi + get() = psi + + override val annotations: List + get() { + val annotatedExpression = psi?.parent as? KtAnnotatedExpression ?: return emptyList() + return annotatedExpression.annotationEntries.map { KotlinUAnnotation(it, this) } + } +} + diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.172 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.172 new file mode 100644 index 00000000000..21f29976598 --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.172 @@ -0,0 +1,292 @@ +/* + * 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 org.jetbrains.kotlin.asJava.classes.KtLightClass +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.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.uast.* +import org.jetbrains.uast.kotlin.declarations.KotlinUMethod +import org.jetbrains.uast.kotlin.declarations.UastLightIdentifier + +abstract class AbstractKotlinUClass(givenParent: UElement?) : KotlinAbstractUElement(givenParent), UClass, JvmDeclarationUElement { + + override val uastDeclarations by lz { + mutableListOf().apply { + addAll(fields) + addAll(initializers) + addAll(methods) + addAll(innerClasses) + } + } + + override val uastSuperTypes: List + get() { + val ktClass = (psi as? KtLightClass)?.kotlinOrigin ?: return emptyList() + return ktClass.superTypeListEntries.mapNotNull { it.typeReference }.map { + LazyKotlinUTypeReferenceExpression(it, this) + } + } + + override val uastAnchor: UElement? + get() = UIdentifier(psi.nameIdentifier, 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() + +} + +open class KotlinUClass private constructor( + psi: KtLightClass, + givenParent: UElement? +) : AbstractKotlinUClass(givenParent), PsiClass by psi { + + 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: UElement + get() = UIdentifier(nameIdentifier, this) + + 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 + + return psi.methods.asSequence() + .filterNot(::isDelegatedMethod) + .map(::createUMethod) + .toList() + .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, 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 javaPsi = psi + + override val sourcePsi = psi.kotlinOrigin + + 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? = (psi as? KtLightClass)?.kotlinOrigin + + 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: UElement? + get() { + val ktClassOrObject = (psi.originalElement as? KtLightClass)?.kotlinOrigin as? KtObjectDeclaration ?: return null + return UIdentifier(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: UElement + get() = UIdentifier(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, + override val uastParent: UElement? + ) : KotlinUMethod(psi, uastParent) { + override val uastBody: UExpression? by lz { + val initializers = script.declarations.filterIsInstance() + KotlinUBlockExpression.create(initializers, this) + } + override val javaPsi = psi + override val sourcePsi = psi.kotlinOrigin + } +} + +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/KotlinUVariable.kt.172 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.172 index e993b8c2625..9495091ce0a 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.172 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.172 @@ -338,7 +338,7 @@ open class KotlinUAnnotatedLocalVariable( override val annotations: List by lz { computeAnnotations(this) } } -class KotlinUEnumConstant( +open class KotlinUEnumConstant( psi: PsiEnumConstant, override val sourcePsi: KtElement?, givenParent: UElement? @@ -350,10 +350,6 @@ class KotlinUEnumConstant( } } - override fun getInitializer(): PsiExpression? = super.getInitializer() - - override fun getOriginalElement(): PsiElement? = super.getOriginalElement() - override val javaPsi = unwrap(psi) override val psi = javaPsi @@ -409,11 +405,15 @@ class KotlinUEnumConstant( private class KotlinEnumConstantClassReference( override val psi: PsiEnumConstant, override val sourcePsi: KtElement?, - givenParent: UElement? + private val givenParent: UElement? ) : KotlinAbstractUExpression(givenParent), USimpleNameReferenceExpression { override val javaPsi: PsiElement? get() = psi + override val uastParent: UElement? by lz { + givenParent ?: KotlinUastLanguagePlugin().convertElementWithParent(psi.parent ?: psi.containingFile, null) + } + override fun resolve() = psi.containingClass override val resolvedName: String? get() = psi.containingClass?.name diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUDeclarationsExpression.kt.172 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUDeclarationsExpression.kt.172 new file mode 100644 index 00000000000..af143b057e6 --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUDeclarationsExpression.kt.172 @@ -0,0 +1,46 @@ +/* + * 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 + +import com.intellij.psi.PsiElement +import org.jetbrains.uast.kotlin.KotlinAbstractUExpression +import org.jetbrains.uast.kotlin.doConvertParent + +open class KotlinUDeclarationsExpression( + override val psi: PsiElement?, + givenParent: UElement?, + val psiAnchor: PsiElement? = null +) : KotlinAbstractUExpression(givenParent), UDeclarationsExpression { + + override val sourcePsi: PsiElement? + get() = psiAnchor + + override val uastParent: UElement? + get() = if (psiAnchor != null) doConvertParent(this, psiAnchor.parent) else super.uastParent + + constructor(uastParent: UElement?) : this(null, uastParent) + + override lateinit var declarations: List + internal set +} + +class KotlinUDestructuringDeclarationExpression( + givenParent: UElement?, + psiAnchor: PsiElement +) : KotlinUDeclarationsExpression(null, givenParent, psiAnchor) { + + val tempVarAssignment get() = declarations.first() +}