From 9efa79f3522bd4a1283425866d78aacdf76728f8 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Thu, 11 Jan 2018 19:38:36 +0300 Subject: [PATCH] 181: Uast: `KotlinUIdentifier` introduced to store proper `sourcePsi` (KT-21688) --- .../kotlin/declarations/KotlinUClass.kt.181 | 290 ++++++++++++ .../kotlin/declarations/KotlinUMethod.kt.181 | 128 +++++ .../declarations/KotlinUVariable.kt.181 | 442 ++++++++++++++++++ .../declarations/UastLightIdentifier.kt.181 | 49 ++ .../tests/AbstractKotlinRenderLogTest.kt.181 | 136 ++++++ 5 files changed, 1045 insertions(+) create mode 100644 plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 create mode 100644 plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.181 create mode 100644 plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 create mode 100644 plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 create mode 100644 plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt.181 diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 new file mode 100644 index 00000000000..88a3ebf1d12 --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 @@ -0,0 +1,290 @@ +/* + * 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.KotlinUIdentifier +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 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() = KotlinUIdentifier(nameIdentifier, ktClass?.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 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: UElement + get() = 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, givenParent) { + 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/KotlinUMethod.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.181 new file mode 100644 index 00000000000..6f2d663985c --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.181 @@ -0,0 +1,128 @@ +/* + * 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.declarations + +import com.intellij.psi.* +import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.asJava.elements.isGetter +import org.jetbrains.kotlin.asJava.elements.isSetter +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.uast.* +import org.jetbrains.uast.java.internal.JavaUElementWithComments +import org.jetbrains.uast.kotlin.* + +open class KotlinUMethod( + psi: KtLightMethod, + givenParent: UElement? +) : KotlinAbstractUElement(givenParent), UAnnotationMethod, JavaUElementWithComments, PsiMethod by psi { + override val psi: KtLightMethod = unwrap(psi) + + override val javaPsi = psi + + override val sourcePsi = psi.kotlinOrigin + + override fun getSourceElement() = sourcePsi ?: this + + override val uastDefaultValue by lz { + val annotationParameter = psi.kotlinOrigin as? KtParameter ?: return@lz null + val defaultValue = annotationParameter.defaultValue ?: return@lz null + getLanguagePlugin().convertElement(defaultValue, this) as? UExpression + } + + private val kotlinOrigin = (psi.originalElement as KtLightElement<*, *>).kotlinOrigin + + override fun getContainingFile(): PsiFile? = unwrapFakeFileForLightClass(psi.containingFile) + + override fun getNameIdentifier() = UastLightIdentifier(psi, kotlinOrigin as KtNamedDeclaration?) + + override val annotations by lz { + psi.annotations + .mapNotNull { (it as? KtLightElement<*, *>)?.kotlinOrigin as? KtAnnotationEntry } + .map { KotlinUAnnotation(it, this) } + } + + private val receiver by lz { (sourcePsi as? KtCallableDeclaration)?.receiverTypeReference } + + override val uastParameters by lz { + val lightParams = psi.parameterList.parameters + val receiver = receiver ?: return@lz lightParams.map { + KotlinUParameter(it, (it as? KtLightElement<*, *>)?.kotlinOrigin, this) + } + val receiverLight = lightParams.firstOrNull() ?: return@lz emptyList() + val uParameters = SmartList(KotlinReceiverUParameter(receiverLight, receiver, this)) + lightParams.drop(1).mapTo(uParameters) { KotlinUParameter(it, (it as? KtLightElement<*, *>)?.kotlinOrigin, this) } + uParameters + } + + override val uastAnchor: UElement + get() = KotlinUIdentifier( + nameIdentifier, + (sourcePsi as? PsiNameIdentifierOwner)?.nameIdentifier ?: sourcePsi?.navigationElement, + this + ) + + + override val uastBody by lz { + val bodyExpression = when (kotlinOrigin) { + is KtFunction -> kotlinOrigin.bodyExpression + is KtProperty -> when { + psi.isGetter -> kotlinOrigin.getter?.bodyExpression + psi.isSetter -> kotlinOrigin.setter?.bodyExpression + else -> null + } + else -> null + } ?: return@lz null + + when (bodyExpression) { + !is KtBlockExpression -> { + KotlinUBlockExpression.KotlinLazyUBlockExpression(this, { block -> + val implicitReturn = KotlinUImplicitReturnExpression(block) + val uBody = getLanguagePlugin().convertElement(bodyExpression, implicitReturn) as? UExpression + ?: return@KotlinLazyUBlockExpression emptyList() + listOf(implicitReturn.apply { returnExpression = uBody }) + }) + + } + else -> getLanguagePlugin().convertElement(bodyExpression, this) as? UExpression + } + } + + override val isOverride: Boolean + get() = (kotlinOrigin as? KtCallableDeclaration)?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false + + override fun getBody(): PsiCodeBlock? = super.getBody() + + override fun getOriginalElement(): PsiElement? = super.getOriginalElement() + + override fun equals(other: Any?) = other is KotlinUMethod && psi == other.psi + + companion object { + fun create(psi: KtLightMethod, containingElement: UElement?) = + if (psi.kotlinOrigin is KtConstructor<*>) { + KotlinConstructorUMethod( + psi.kotlinOrigin?.containingClassOrObject, + psi, containingElement + ) + } + else + KotlinUMethod(psi, containingElement) + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 new file mode 100644 index 00000000000..40b5bc93901 --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 @@ -0,0 +1,442 @@ +/* + * 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.search.GlobalSearchScope +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.calls.callUtil.getType +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.TypeNullability +import org.jetbrains.kotlin.types.typeUtil.nullability +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.uast.* +import org.jetbrains.uast.internal.acceptList +import org.jetbrains.uast.kotlin.declarations.KotlinUIdentifier +import org.jetbrains.uast.kotlin.declarations.UastLightIdentifier +import org.jetbrains.uast.kotlin.internal.KotlinUElementWithComments +import org.jetbrains.uast.kotlin.psi.UastKotlinPsiParameter +import org.jetbrains.uast.kotlin.psi.UastKotlinPsiVariable +import org.jetbrains.uast.visitor.UastVisitor + +abstract class AbstractKotlinUVariable(givenParent: UElement?) + : KotlinAbstractUElement(givenParent), PsiVariable, UVariable, KotlinUElementWithComments { + + override val uastInitializer: UExpression? + get() { + val psi = psi + val initializerExpression = when (psi) { + is UastKotlinPsiVariable -> psi.ktInitializer + is UastKotlinPsiParameter -> psi.ktDefaultValue + is KtLightElement<*, *> -> { + val origin = psi.kotlinOrigin + when (origin) { + is KtVariableDeclaration -> origin.initializer + is KtParameter -> origin.defaultValue + else -> null + } + } + else -> null + } ?: return null + return getLanguagePlugin().convertElement(initializerExpression, this) as? UExpression ?: UastEmptyExpression + } + + val delegateExpression: UExpression? by lz { + val psi = psi + val expression = when (psi) { + is KtLightElement<*, *> -> (psi.kotlinOrigin as? KtProperty)?.delegateExpression + is UastKotlinPsiVariable -> (psi.ktElement as? KtProperty)?.delegateExpression + else -> null + } + + expression?.let { getLanguagePlugin().convertElement(it, this) as? UExpression } + } + + override fun getNameIdentifier(): PsiIdentifier { + val kotlinOrigin = (psi as? KtLightElement<*, *>)?.kotlinOrigin + return UastLightIdentifier(psi, kotlinOrigin as KtNamedDeclaration?) + } + + override fun getContainingFile(): PsiFile = unwrapFakeFileForLightClass(psi.containingFile) + + override val annotations by lz { + val sourcePsi = sourcePsi ?: return@lz psi.annotations.map { WrappedUAnnotation(it, this) } + val annotations = SmartList(KotlinNullabilityUAnnotation(sourcePsi, this)) + if (sourcePsi is KtModifierListOwner) { + sourcePsi.annotationEntries. + filter { acceptsAnnotationTarget(it.useSiteTarget?.getAnnotationUseSiteTarget()) }. + mapTo(annotations) { KotlinUAnnotation(it, this) } + } + annotations + } + + + abstract protected fun acceptsAnnotationTarget(target: AnnotationUseSiteTarget?): Boolean + + override val typeReference by lz { getLanguagePlugin().convertOpt(psi.typeElement, this) } + + override val uastAnchor: UElement? + get() = KotlinUIdentifier(nameIdentifier, sourcePsi, this) + + override fun equals(other: Any?) = other is AbstractKotlinUVariable && psi == other.psi + + class WrappedUAnnotation(psiAnnotation: PsiAnnotation, override val uastParent: UElement) : UAnnotation, JvmDeclarationUElement { + + override val javaPsi: PsiAnnotation = psiAnnotation + override val psi: PsiAnnotation = javaPsi + override val sourcePsi: PsiElement? = null + + override val attributeValues: List by lz { + psi.parameterList.attributes.map { WrappedUNamedExpression(it, this) } + } + + class WrappedUNamedExpression(pair: PsiNameValuePair, override val uastParent: UElement?) : UNamedExpression, JvmDeclarationUElement { + override val name: String? = pair.name + override val psi = pair + override val javaPsi: PsiElement? = psi + override val sourcePsi: PsiElement? = null + override val annotations: List = emptyList() + override val expression: UExpression by lz { toUExpression(psi.value) } + } + + override val qualifiedName: String? = psi.qualifiedName + override fun findAttributeValue(name: String?): UExpression? = psi.findAttributeValue(name)?.let { toUExpression(it) } + override fun findDeclaredAttributeValue(name: String?): UExpression? = psi.findDeclaredAttributeValue(name)?.let { toUExpression(it) } + override fun resolve(): PsiClass? = psi.nameReferenceElement?.resolve() as? PsiClass + } + +} + +private fun toUExpression(psi: PsiElement?): UExpression = psi.toUElementOfType() ?: UastEmptyExpression + +class KotlinUVariable( + psi: PsiVariable, + override val sourcePsi: KtElement, + givenParent: UElement? +) : AbstractKotlinUVariable(givenParent), UVariable, PsiVariable by psi { + + override val javaPsi = unwrap(psi) + + override val psi = javaPsi + + override val typeReference by lz { getLanguagePlugin().convertOpt(psi.typeElement, this) } + + override fun acceptsAnnotationTarget(target: AnnotationUseSiteTarget?): Boolean = true + + override fun getInitializer(): PsiExpression? { + return super.getInitializer() + } + + override fun getOriginalElement(): PsiElement? { + return super.getOriginalElement() + } + + override fun getNameIdentifier(): PsiIdentifier { + return super.getNameIdentifier() + } + + override fun getContainingFile(): PsiFile { + return super.getContainingFile() + } + +} + +open class KotlinUParameter( + psi: PsiParameter, + final override val sourcePsi: KtElement?, + givenParent: UElement? +) : AbstractKotlinUVariable(givenParent), UParameter, PsiParameter by psi { + + final override val javaPsi = unwrap(psi) + + override val psi = javaPsi + + private val isLightConstructorParam by lz { psi.getParentOfType(true)?.isConstructor } + + private val isKtConstructorParam by lz { sourcePsi?.getParentOfType(true)?.let { it is KtConstructor<*> } } + + override fun acceptsAnnotationTarget(target: AnnotationUseSiteTarget?): Boolean { + if (sourcePsi !is KtParameter) return false + if (isKtConstructorParam == isLightConstructorParam && target == null) return true + when (target) { + AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER -> return isLightConstructorParam == true + AnnotationUseSiteTarget.SETTER_PARAMETER -> return isLightConstructorParam != true + else -> return false + } + } + + override fun getInitializer(): PsiExpression? { + return super.getInitializer() + } + + override fun getOriginalElement(): PsiElement? { + return super.getOriginalElement() + } + + override fun getNameIdentifier(): PsiIdentifier { + return super.getNameIdentifier() + } + + override fun getContainingFile(): PsiFile { + return super.getContainingFile() + } +} + +class KotlinReceiverUParameter( + psi: PsiParameter, + private val receiver: KtTypeReference, + givenParent: UElement? +) : KotlinUParameter(psi, receiver, givenParent) { + + override val annotations: List by lz { + receiver.annotationEntries + .filter { it.useSiteTarget?.getAnnotationUseSiteTarget() == AnnotationUseSiteTarget.RECEIVER } + .map { KotlinUAnnotation(it, this) } + + super.annotations + } + +} + +class KotlinNullabilityUAnnotation(val annotatedElement: PsiElement, override val uastParent: UElement) : UAnnotation, JvmDeclarationUElement { + + private fun getTargetType(annotatedElement: PsiElement): KotlinType? { + if (annotatedElement is KtTypeReference) { + annotatedElement.getType()?.let { return it } + } + if (annotatedElement is KtCallableDeclaration) { + annotatedElement.typeReference?.getType()?.let { return it } + } + if (annotatedElement is KtProperty) { + annotatedElement.initializer?.let { it.getType(it.analyze()) }?.let { return it } + annotatedElement.delegateExpression?.let { it.getType(it.analyze())?.arguments?.firstOrNull()?.type }?.let { return it } + } + annotatedElement.getParentOfType(false)?.let { + it.typeReference?.getType() ?: it.initializer?.let { it.getType(it.analyze()) } + }?.let { return it } + return null + } + + val nullability by lz { getTargetType(annotatedElement)?.nullability() } + + override val attributeValues: List + get() = emptyList() + override val psi: PsiElement? + get() = null + override val javaPsi: PsiAnnotation? + get() = null + override val sourcePsi: PsiElement? + get() = null + override val qualifiedName: String? + get() = when (nullability) { + TypeNullability.NOT_NULL -> NotNull::class.qualifiedName + TypeNullability.NULLABLE -> Nullable::class.qualifiedName + TypeNullability.FLEXIBLE -> null + null -> null + } + + override fun findAttributeValue(name: String?): UExpression? = null + + override fun findDeclaredAttributeValue(name: String?): UExpression? = null + + override fun resolve(): PsiClass? = qualifiedName?.let { + val project = annotatedElement.project + JavaPsiFacade.getInstance(project).findClass(it, GlobalSearchScope.allScope(project)) + } + +} + +open class KotlinUField( + psi: PsiField, + override val sourcePsi: KtElement?, + givenParent: UElement? +) : AbstractKotlinUVariable(givenParent), UField, PsiField by psi { + override fun getSourceElement() = sourcePsi ?: this + + override val javaPsi = unwrap(psi) + + override val psi = javaPsi + + override fun acceptsAnnotationTarget(target: AnnotationUseSiteTarget?): Boolean = + target == AnnotationUseSiteTarget.FIELD || + target == AnnotationUseSiteTarget.PROPERTY_DELEGATE_FIELD || + (sourcePsi is KtProperty) && (target == null || target == AnnotationUseSiteTarget.PROPERTY) + + override fun getInitializer(): PsiExpression? { + return super.getInitializer() + } + + override fun getOriginalElement(): PsiElement? { + return super.getOriginalElement() + } + + override fun getNameIdentifier(): PsiIdentifier { + return super.getNameIdentifier() + } + + override fun getContainingFile(): PsiFile { + return super.getContainingFile() + } + + override fun isPhysical(): Boolean { + return true + } + + override fun accept(visitor: UastVisitor) { + if (visitor.visitField(this)) return + annotations.acceptList(visitor) + uastInitializer?.accept(visitor) + delegateExpression?.accept(visitor) + visitor.afterVisitField(this) + } +} + +open class KotlinULocalVariable( + psi: PsiLocalVariable, + override val sourcePsi: KtElement, + givenParent: UElement? +) : AbstractKotlinUVariable(givenParent), ULocalVariable, PsiLocalVariable by psi { + + override val javaPsi = unwrap(psi) + + override fun acceptsAnnotationTarget(target: AnnotationUseSiteTarget?): Boolean = true + + override val psi = javaPsi + + override fun getInitializer(): PsiExpression? { + return super.getInitializer() + } + + override fun getOriginalElement(): PsiElement? { + return super.getOriginalElement() + } + + override fun getNameIdentifier(): PsiIdentifier { + return super.getNameIdentifier() + } + + override fun getContainingFile(): PsiFile { + return super.getContainingFile() + } + + override fun accept(visitor: UastVisitor) { + if (visitor.visitLocalVariable(this)) return + annotations.acceptList(visitor) + uastInitializer?.accept(visitor) + delegateExpression?.accept(visitor) + visitor.afterVisitLocalVariable(this) + } +} + +open class KotlinUAnnotatedLocalVariable( + psi: PsiLocalVariable, + sourcePsi: KtElement, + uastParent: UElement?, + computeAnnotations: (parent: UElement) -> List +) : KotlinULocalVariable(psi, sourcePsi, uastParent) { + + override val annotations: List by lz { computeAnnotations(this) } +} + +class KotlinUEnumConstant( + psi: PsiEnumConstant, + override val sourcePsi: KtElement?, + givenParent: UElement? +) : AbstractKotlinUVariable(givenParent), UEnumConstant, PsiEnumConstant by psi { + + override val initializingClass: UClass? by lz { + (psi.initializingClass as? KtLightClass)?.let { initializingClass -> + KotlinUClass.create(initializingClass, this) + } + } + + override fun getInitializer(): PsiExpression? = super.getInitializer() + + override fun getOriginalElement(): PsiElement? = super.getOriginalElement() + + override val javaPsi = unwrap(psi) + + override val psi = javaPsi + + override fun acceptsAnnotationTarget(target: AnnotationUseSiteTarget?): Boolean = true + + override fun getContainingFile(): PsiFile { + return super.getContainingFile() + } + + override fun getNameIdentifier(): PsiIdentifier { + return super.getNameIdentifier() + } + + override val kind: UastCallKind + get() = UastCallKind.CONSTRUCTOR_CALL + + override val receiver: UExpression? + get() = null + + override val receiverType: PsiType? + get() = null + + override val methodIdentifier: UIdentifier? + get() = null + + override val classReference: UReferenceExpression? + get() = KotlinEnumConstantClassReference(psi, sourcePsi, this) + + override val typeArgumentCount: Int + get() = 0 + + override val typeArguments: List + get() = emptyList() + + override val valueArgumentCount: Int + get() = psi.argumentList?.expressions?.size ?: 0 + + override val valueArguments by lz { + psi.argumentList?.expressions?.map { + getLanguagePlugin().convertElement(it, this) as? UExpression ?: UastEmptyExpression + } ?: emptyList() + } + + override val returnType: PsiType? + get() = psi.type + + override fun resolve() = psi.resolveMethod() + + override val methodName: String? + get() = null + + private class KotlinEnumConstantClassReference( + override val psi: PsiEnumConstant, + override val sourcePsi: KtElement?, + givenParent: UElement? + ) : KotlinAbstractUExpression(givenParent), USimpleNameReferenceExpression { + override val javaPsi: PsiElement? + get() = psi + + override fun resolve() = psi.containingClass + override val resolvedName: String? + get() = psi.containingClass?.name + override val identifier: String + get() = psi.containingClass?.name ?: "" + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 new file mode 100644 index 00000000000..110419ffc51 --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2017 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.declarations + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiIdentifier +import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UIdentifier +import org.jetbrains.uast.kotlin.unwrapFakeFileForLightClass + +class UastLightIdentifier(lightOwner: PsiNameIdentifierOwner, ktDeclaration: KtNamedDeclaration?) : + KtLightIdentifier(lightOwner, ktDeclaration) { + override fun getContainingFile(): PsiFile = unwrapFakeFileForLightClass(super.getContainingFile()) +} + +class KotlinUIdentifier private constructor( + override val javaPsi: PsiIdentifier?, + override val sourcePsi: PsiElement?, + override val psi: PsiElement?, + override val uastParent: UElement? +) : UIdentifier(psi, uastParent) { + + init { + assert(sourcePsi == null || sourcePsi is LeafPsiElement || sourcePsi is KtElement, { "sourcePsi should be physical" }) + } + + constructor(javaPsi: PsiIdentifier?, sourcePsi: PsiElement?, uastParent: UElement?) : this(javaPsi, sourcePsi, javaPsi, uastParent) + constructor(sourcePsi: PsiElement?, uastParent: UElement?) : this(null, sourcePsi, sourcePsi, uastParent) +} \ No newline at end of file diff --git a/plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt.181 b/plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt.181 new file mode 100644 index 00000000000..2043926a82c --- /dev/null +++ b/plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt.181 @@ -0,0 +1,136 @@ +package org.jetbrains.uast.test.kotlin + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiRecursiveElementVisitor +import com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.utils.addToStdlib.assertedCast +import org.jetbrains.uast.JvmDeclarationUElement +import org.jetbrains.uast.UDeclaration +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UFile +import org.jetbrains.uast.kotlin.KOTLIN_CACHED_UELEMENT_KEY +import org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin +import org.jetbrains.uast.test.common.RenderLogTestBase +import org.jetbrains.uast.visitor.UastVisitor +import org.junit.Assert +import java.io.File +import java.util.* + +abstract class AbstractKotlinRenderLogTest : AbstractKotlinUastTest(), RenderLogTestBase { + override fun getTestFile(testName: String, ext: String) = + File(File(TEST_KOTLIN_MODEL_DIR, testName).canonicalPath + '.' + ext) + + override fun check(testName: String, file: UFile) { + check(testName, file, true) + } + + fun check(testName: String, file: UFile, checkParentConsistency: Boolean) { + super.check(testName, file) + + if (checkParentConsistency) { + checkParentConsistency(file) + } + + file.checkContainingFileForAllElements() + file.checkJvmDeclarationsImplementations() + } + + private fun checkParentConsistency(file: UFile) { + val parentMap = mutableMapOf() + + file.accept(object : UastVisitor { + private val parentStack = Stack() + + override fun visitElement(node: UElement): Boolean { + val parent = node.uastParent + if (parent == null) { + Assert.assertTrue("Wrong parent of $node", parentStack.empty()) + } + else { + Assert.assertEquals("Wrong parent of $node", parentStack.peek(), parent) + } + node.psi?.let { + if (it !in parentMap) { + parentMap[it] = parentStack.reversed().joinToString { it.asLogString() } + } + } + parentStack.push(node) + return false + } + + override fun afterVisitElement(node: UElement) { + super.afterVisitElement(node) + parentStack.pop() + } + }) + + file.psi.clearUastCaches() + + file.psi.accept(object : PsiRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + val uElement = KotlinUastLanguagePlugin().convertElementWithParent(element, null) + val expectedParents = parentMap[element] + if (expectedParents != null) { + assertNotNull("Expected to be able to convert PSI element $element", uElement) + val parents = generateSequence(uElement!!.uastParent) { it.uastParent }.joinToString { it.asLogString() } + assertEquals("Inconsistent parents for ${uElement.asRenderString()}(${uElement.asLogString()})(${uElement.javaClass}) (converted from $element[${element.text}])", expectedParents, parents) + } + super.visitElement(element) + } + }) + } + + private fun UFile.checkContainingFileForAllElements() { + accept(object : UastVisitor { + override fun visitElement(node: UElement): Boolean { + if (node is PsiElement) { + node.containingFile.assertedCast { "containingFile should be KtFile for ${node.asLogString()}" } + } + + val anchorPsi = (node as? UDeclaration)?.uastAnchor?.psi + if (anchorPsi != null) { + anchorPsi.containingFile.assertedCast { "uastAnchor.containingFile should be KtFile for ${node.asLogString()}" } + } + + return false + } + }) + } + + private fun UFile.checkJvmDeclarationsImplementations() { + accept(object : UastVisitor { + override fun visitElement(node: UElement): Boolean { + + if (node is UDeclaration) {// visitDeclaration hasn't come yet + node.uastAnchor?.let { visitElement(it) } + } + + val jvmDeclaration = node as? JvmDeclarationUElement + ?: throw AssertionError("${node.javaClass} should implement 'JvmDeclarationUElement'") + + jvmDeclaration.sourcePsi?.let { + assertTrue("sourcePsi should be physical but ${it.javaClass} found for [${it.text}] " + + "for ${jvmDeclaration.javaClass}->${jvmDeclaration.uastParent?.javaClass}",it is LeafPsiElement || it is KtElement|| it is LeafPsiElement) + } + jvmDeclaration.javaPsi?.let { + assertTrue("javaPsi should be light but ${it.javaClass} found for [${it.text}] " + + "for ${jvmDeclaration.javaClass}->${jvmDeclaration.uastParent?.javaClass}", it !is KtElement) + } + + return false + } + }) + } +} + +private fun PsiFile.clearUastCaches() { + accept(object : PsiRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + super.visitElement(element) + element.putUserData(KOTLIN_CACHED_UELEMENT_KEY, null) + } + }) +}