From 8bd49c147cf29ec6509e1736fcefaaf97a82f0bf Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Tue, 12 Mar 2019 12:30:20 +0300 Subject: [PATCH] New J2K: Fix convert getters and setters --- .../ConvertGettersAndSetters.kt | 451 +++++++++++------- .../kotlin/nj2k/postProcessing/utils.kt | 44 +- 2 files changed, 306 insertions(+), 189 deletions(-) diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/ConvertGettersAndSetters.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/ConvertGettersAndSetters.kt index 6dfb50b35a6..35c79033cc3 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/ConvertGettersAndSetters.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/ConvertGettersAndSetters.kt @@ -8,16 +8,24 @@ package org.jetbrains.kotlin.nj2k.postProcessing import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.MemberDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.core.setVisibility import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass -import org.jetbrains.kotlin.nj2k.NewJ2kPostProcessing +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.lexer.KtKeywordToken +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.nj2k.NewJ2kPostProcessing import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.addRemoveModifier.setModifierList import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics class ConvertGettersAndSetters : NewJ2kPostProcessing { @@ -29,41 +37,67 @@ class ConvertGettersAndSetters : NewJ2kPostProcessing { var setter: Accessor? = null ) + data class PropertyDataExtended( + val name: String, + val getter: Accessor, + val setter: Accessor?, + val needOverride: Boolean, + val target: KtProperty, + val visibility: KtModifierKeywordToken + ) + data class Accessor( val name: String, val target: KtProperty?, - val function: KtFunction + val ktFunction: KtFunction, + val isPure: Boolean, + val isFromCurrentDeclaration: Boolean ) - private fun Accessor.isTrivial() = target != null - private fun KtExpression.statements() = if (this is KtBlockExpression) statements else listOf(this) - private fun KtFunction.asGetter(): Accessor? { + + private fun KtFunction.getGetterTarget(): KtProperty? = + collectDescendantsOfType() + .singleOrNull() + ?.returnedExpression + ?.unpackedReferenceToProperty() + ?.takeIf { + it.type() == this.type()!! + } + + + private fun KtFunction.asGetter(klass: KtClassOrObject): Accessor? { if (valueParameters.isNotEmpty()) return null if (typeParameters.isNotEmpty()) return null val name = getterName() ?: return null - val target = bodyExpression - ?.statements() - ?.singleOrNull() - ?.let { - if (it is KtReturnExpression) it.returnedExpression - else it - }?.let { - it.unpackedReferenceToProperty() - }?.takeIf { - it.type() == this.type() - } - return Accessor(name, target, this) + val target = getGetterTarget() + val isPure = target != null + && bodyExpression?.statements()?.size == 1 + + return Accessor( + name, + target, + this, + isPure, + isFromCurrentDeclaration = containingClassOrObject == klass + ) } + private inline fun KtClassOrObject.declarations(): List

= + (analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, this] as ClassDescriptor) + .unsubstitutedMemberScope.getContributedDescriptors { true } + .filterIsInstance() + .mapNotNull { it.findPsi() as? P } + + private fun KtFunction.isProcedure() = bodyExpression is KtBlockExpression? && !hasDeclaredReturnType() - || getReturnTypeReference()?.typeElement!!.text == "Unit" + || getReturnTypeReference()?.typeElement?.text == "Unit" - private fun KtFunction.asSetter(): Accessor? { + private fun KtFunction.asSetter(klass: KtClassOrObject): Accessor? { if (typeParameters.isNotEmpty()) return null if (valueParameters.size != 1) return null if (!isProcedure()) return null @@ -74,15 +108,21 @@ class ConvertGettersAndSetters : NewJ2kPostProcessing { ?.singleOrNull() ?.let { if (it is KtBinaryExpression) { - if (it.operationToken != KtTokens.EQEQ) return@let null + if (it.operationToken != KtTokens.EQ) return@let null val right = it.right as? KtNameReferenceExpression ?: return@let null - if (right.reference?.resolve() != valueParameters.single()) return@let null + if (right.resolve() != valueParameters.single()) return@let null it.left?.unpackedReferenceToProperty() } else null }?.takeIf { it.type() == valueParameters.single().type() } - return Accessor(name, target, this) + return Accessor( + name, + target, + this, + isPure = target != null || bodyExpression?.statements()?.isEmpty() == true, + isFromCurrentDeclaration = containingClassOrObject == klass + ) } private val keywords = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet() @@ -107,108 +147,180 @@ class ConvertGettersAndSetters : NewJ2kPostProcessing { ?.decapitalize() ?.escaped() - private fun generatePropertiesData(element: KtClassOrObject): List { + private fun KtClassOrObject.collectGetterAndSettersPairs(): Sequence { val properties = mutableMapOf() + val functions = declarations() - for (declaration in element.declarations) { - when (declaration) { - is KtFunction -> { - declaration.asGetter() - ?.also { getter -> - properties.getOrPut(getter.name.removePrefix("is").decapitalize()) { - PropertyData(getter.name) - }.also { it.getter = getter } - } - - declaration.asSetter() - ?.also { setter -> - properties.getOrPut(setter.name) { - PropertyData(setter.name) - }.also { it.setter = setter } - } + for (declaration in functions) { + declaration.asGetter(this) + ?.also { getter -> + properties.getOrPut(getter.name.removePrefix("is").decapitalize()) { + PropertyData(getter.name) + }.also { it.getter = getter } + } + + declaration.asSetter(this) + ?.also { setter -> + properties.getOrPut(setter.name) { + PropertyData(setter.name) + }.also { it.setter = setter } } - } } - return properties.values.toList() + return properties.values.asSequence() } - private fun T.replaceBodyText( + private fun List.maxVisibility(): KtModifierKeywordToken = + map { it.visibilityModifierTypeOrDefault() } + .maxBy { + when (it) { + KtTokens.PUBLIC_KEYWORD -> 4 + KtTokens.INTERNAL_KEYWORD -> 3 + KtTokens.PROTECTED_KEYWORD -> 2 + KtTokens.PRIVATE_KEYWORD -> 1 + else -> 0 + } + } ?: KtTokens.DEFAULT_VISIBILITY_KEYWORD + + private fun generatePropertiesData(klass: KtClassOrObject): List { + val propertyByName = klass + .declarations() + .map { it.name!! to it } + .toMap() + val factory = KtPsiFactory(klass) + + + return klass.collectGetterAndSettersPairs().mapNotNull { (name, getter, setter) -> + if (getter == null) return@mapNotNull null + + val referencedProperty = propertyByName[name] + if (referencedProperty != null + && getter.target != referencedProperty + && referencedProperty.containingClassOrObject == klass + ) return@mapNotNull null + + val setterAndGetterHaveTheSameTypes = + setter != null + && setter.ktFunction.valueParameters.single().type() == getter.ktFunction.type() + + if (klass.isInterfaceClass() + && setter != null + && !setterAndGetterHaveTheSameTypes + ) return@mapNotNull null + + val needOverride = + getter.target?.hasModifier(KtTokens.OVERRIDE_KEYWORD) == true + || getter.ktFunction.hasModifier(KtTokens.OVERRIDE_KEYWORD) + || setter?.ktFunction?.hasModifier(KtTokens.OVERRIDE_KEYWORD) == true + + val realTarget = + getter.target?.takeIf { + !it.hasUsagesOutsideOf(klass.containingKtFile, listOfNotNull(getter.ktFunction, setter?.ktFunction)) + || getter.isPure && setter?.isPure != false + } + ?.takeIf { + getter.isFromCurrentDeclaration + } + + if (getter.target?.visibilityModifierType() == KtTokens.PUBLIC_KEYWORD + && (!getter.isPure || setter?.isPure == false) + ) return@mapNotNull null + + if (realTarget == null + && referencedProperty != null + && referencedProperty.containingClassOrObject == klass + ) return@mapNotNull null + + val target = realTarget +// ?.takeIf { +// getter.isFromCurrentDeclaration +// } + ?: factory.createProperty( + name, + getter.ktFunction.getReturnTypeReference()?.text, + isVar = true + ) + + val visibility = + listOfNotNull(setter?.ktFunction, getter.ktFunction, target).maxVisibility() + + + + PropertyDataExtended( + name, + getter, + setter?.takeIf { setterAndGetterHaveTheSameTypes }, + needOverride, + target, + visibility + ) + }.toList() + } + + private fun KtExpression.isReferenceToThis() = + when (this) { + is KtThisExpression -> instanceReference.resolve() == parentOfType() + is KtReferenceExpression -> resolve() == parentOfType() + else -> false + } + + private fun T.withReplacedExpressionInBody( from: KtElement, to: KtExpression, replaceOnlyWriteUsages: Boolean - ): T = - also { - from.usages() - .map { it.element } - .filter { it.isInsideOf(listOf(this)) } - .forEach { reference -> - val parent = reference.parent - val referenceExpression = when { - parent is KtQualifiedExpression && parent.receiverExpression is KtThisExpression -> - parent - else -> reference - } - if (!replaceOnlyWriteUsages - || (referenceExpression.parent as? KtExpression)?.asAssignment()?.left == referenceExpression - ) { - referenceExpression.replace(to) - } + ): T = also { + from.usages() + .map { it.element } + .filter { it.isInsideOf(listOf(this)) } + .forEach { reference -> + val parent = reference.parent + val referenceExpression = when { + parent is KtQualifiedExpression + && parent.receiverExpression.isReferenceToThis() -> + parent + else -> reference } - } - - - private fun KtPsiFactory.createGetter(expression: KtExpression?): KtPropertyAccessor { - val property = - createProperty("val x get" + if (expression == null) "" else if (expression is KtBlockExpression) "() { return 1 }" else "() = 1") - val getter = property.getter!! - val bodyExpression = getter.bodyExpression - - bodyExpression?.replace(expression!!) - return getter + if (!replaceOnlyWriteUsages + || (referenceExpression.parent as? KtExpression)?.asAssignment()?.left == referenceExpression + ) { + referenceExpression.replace(to) + } + } } private fun KtProperty.addGetter(getterFunction: Accessor, target: KtProperty): KtPropertyAccessor { val factory = KtPsiFactory(this) val newBody = - if (!getterFunction.isTrivial()) { - getterFunction.function.bodyExpression - ?.replaceBodyText(target, factory.createExpression("field"), replaceOnlyWriteUsages = false) - } else null + if (getterFunction.isPure && getterFunction.name == getterFunction.target?.name) null + else { + getterFunction.ktFunction.bodyExpression + ?.withReplacedExpressionInBody(target, factory.createExpression("field"), replaceOnlyWriteUsages = false) + } add(factory.createGetter(newBody)) - getterFunction.function.modifierList?.also { + getterFunction.ktFunction.modifierList?.also { getter!!.addModifiers(it) } return getter!! } + private fun KtPropertyAccessor.addModifiers(newModifiers: KtModifierList) { setModifierList(newModifiers) removeModifier(KtTokens.OVERRIDE_KEYWORD) removeModifier(KtTokens.FINAL_KEYWORD) + removeModifier(KtTokens.ABSTRACT_KEYWORD) } - private fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String): KtPropertyAccessor { - val property = when (body) { - null -> createProperty("var x = 1\n get() = 1\n set") - is KtBlockExpression -> createProperty("var x get() = 1\nset($fieldName) {\n field = $fieldName\n }") - else -> createProperty("var x get() = 1\nset($fieldName) = TODO()") - } - val setter = property.setter!! - if (body != null) { - setter.bodyExpression?.replace(body) - } - return setter - } private fun KtProperty.addSetter(setterInfo: Accessor): KtPropertyAccessor { val factory = KtPsiFactory(this) val newBody = - if (!setterInfo.isTrivial()) { - setterInfo.function.bodyExpression?.replaceBodyText(this, factory.createExpression("field"), true) - } else null - add(factory.createSetter(newBody, setterInfo.function.valueParameters.single().name!!)) - setterInfo.function.modifierList?.also { + if (setterInfo.isPure && setterInfo.name == setterInfo.target?.name) null + else { + setterInfo.ktFunction.bodyExpression?.withReplacedExpressionInBody(this, factory.createExpression("field"), true) + } + add(factory.createSetter(newBody, setterInfo.ktFunction.valueParameters.single().name!!)) + setterInfo.ktFunction.modifierList?.also { setter!!.addModifiers(it) } return setter!! @@ -221,124 +333,97 @@ class ConvertGettersAndSetters : NewJ2kPostProcessing { private fun KtElement.usages() = ReferencesSearch.search(this, LocalSearchScope(containingKtFile)) - .findAll() + private fun KtProperty.addGetterAndChangeAllUsages(getter: Accessor, name: String) = + addGetter(getter, this).also { + val factory = KtPsiFactory(this) + getter.ktFunction.forAllUsages { usage -> + usage.parentOfType()!!.replace(factory.createExpression(name)) + } + } + + private fun KtProperty.addSetterAndChangeAllUsages(setter: Accessor) = + addSetter(setter).also { + val factory = KtPsiFactory(this) + setter.ktFunction.forAllUsages { usage -> + val callExpression = usage.parentOfType()!! + val qualifier = callExpression.getQualifiedExpressionForSelector() + val newValue = callExpression.valueArguments.single() + if (qualifier != null) { + qualifier.replace(factory.createExpression("${qualifier.receiverExpression.text}.$name = ${newValue.text}")) + } else { + callExpression.replace(factory.createExpression("$name = ${newValue.text}")) + } + } + } + + private fun KtProperty.addDefaultSetter() = + KtPsiFactory(this).createSetter(null, "value").let { setter -> + this.add(setter) as KtPropertyAccessor + } + override fun createAction(element: KtElement, diagnostics: Diagnostics): (() -> Unit)? { if (element !is KtClassOrObject) return null - return { val factory = KtPsiFactory(element) - for ((name, getterFunction, setterFunction) in generatePropertiesData(element)) { - if (getterFunction == null) continue - - if (!getterFunction.isTrivial() && - element.declarations.any { (it as? KtProperty)?.name == name } - ) {//TODO improve?? - continue - } - - val setterAndGetterHaveTheSameTypes = - setterFunction != null && - setterFunction.function.valueParameters.single().type() == getterFunction.function.type() - - if (element.isInterfaceClass() && setterFunction != null && !setterAndGetterHaveTheSameTypes) { - continue - } - - val hasOverrideModifier = - getterFunction.target?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false - || getterFunction.function.hasModifier(KtTokens.OVERRIDE_KEYWORD) - || setterFunction?.function?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false - - val target = getterFunction.target - ?: factory.createProperty( - name, - getterFunction.function.getReturnTypeReference()?.text, - true - ) - + for ((name, + getterAccessor, + setterAccessor, + needOverride, + target, + visibility + ) in generatePropertiesData(element)) { if (target.name != name) {//TODO use refactoring target.forAllUsages { usage -> val parent = (usage.parent as? KtExpression) ?.asAssignment() ?.takeIf { it.left == usage } - val expression = if (parent != null) factory.createExpression("this.$name") - else factory.createExpression(name) + val expression = + if (parent != null) factory.createExpression("this.$name") + else factory.createExpression(name) usage.replace(expression) } target.setName(name) } - - val propertyGetter = getterFunction.let { - val getter = target.addGetter(it, target) - it.function.forAllUsages { usage -> - usage.parentOfType()!!.replace(factory.createExpression(name)) - } - getter + target.addGetterAndChangeAllUsages(getterAccessor, name).also { getter -> + getter.setVisibility(visibility) } - val propertySetter = setterFunction - ?.takeIf { setterAndGetterHaveTheSameTypes } - ?.let { - val setter = target.addSetter(it) - it.function.forAllUsages { usage -> - val callExpression = usage.parentOfType()!! - val qualifier = callExpression.getQualifiedExpressionForSelector() - val newValue = callExpression.valueArguments.single() - if (qualifier != null) { - qualifier - .replace(factory.createExpression("${qualifier.receiverExpression.text}.${target.name} = ${newValue.text}")) - } else { - callExpression.replace(factory.createExpression("${target.name} = ${newValue.text}")) - } - } - it.function.delete() - setter - } - ?: if (target.isVar - && target.visibilityModifierTypeOrDefault() in listOf(KtTokens.PRIVATE_KEYWORD, KtTokens.PROTECTED_KEYWORD) - ) { - factory.createSetter(null, "value").apply { - setVisibility(target.visibilityModifierTypeOrDefault()) - target.add(this) - } - target.setter!! - } else null - + val propertySetter = + setterAccessor + ?.takeIf { it.isFromCurrentDeclaration } + ?.let { target.addSetterAndChangeAllUsages(it) } + ?: if (target.isVar + && (target.visibilityModifierTypeOrDefault() == KtTokens.PRIVATE_KEYWORD + || target.visibilityModifierTypeOrDefault() == KtTokens.PROTECTED_KEYWORD) + ) target.addDefaultSetter().also { it.setVisibility(target.visibilityModifierTypeOrDefault()) } + else null + target.setVisibility(visibility) val isVar = propertySetter != null if (target.isVar != isVar) { - target.valOrVarKeyword - .replace(if (isVar) factory.createVarKeyword() else factory.createValKeyword()) + target.valOrVarKeyword.replace(if (isVar) factory.createVarKeyword() else factory.createValKeyword()) } - - val visibility = listOfNotNull( - propertySetter?.visibilityModifierTypeOrDefault(), - propertyGetter.visibilityModifierTypeOrDefault(), - target.visibilityModifierTypeOrDefault() - ).maxBy { - when (it) { - KtTokens.PUBLIC_KEYWORD -> 4 - KtTokens.INTERNAL_KEYWORD -> 3 - KtTokens.PROTECTED_KEYWORD -> 2 - KtTokens.PRIVATE_KEYWORD -> 1 - else -> 0 + if (target.parent?.isPhysical != true) { + when { + getterAccessor.isFromCurrentDeclaration -> + element.addDeclarationAfter(target, getterAccessor.ktFunction) + setterAccessor?.isFromCurrentDeclaration == true -> + element.addDeclarationAfter(target, setterAccessor.ktFunction) } - }!! - target.setVisibility(visibility) - - if (getterFunction.target == null) { - element.addDeclarationAfter(target, getterFunction.function) - getterFunction.function.delete() - } else { - getterFunction.function.delete() } - if (!target.hasModifier(KtTokens.OVERRIDE_KEYWORD) && hasOverrideModifier) { + if (getterAccessor.isFromCurrentDeclaration) { + getterAccessor.ktFunction.delete() + } + if (setterAccessor?.isFromCurrentDeclaration == true) { + setterAccessor.ktFunction.delete() + } + if (!target.hasModifier(KtTokens.OVERRIDE_KEYWORD) && needOverride) { target.addModifier(KtTokens.OVERRIDE_KEYWORD) } } diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/utils.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/utils.kt index 2f58bb6a59d..b635922ee35 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/utils.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/utils.kt @@ -10,15 +10,16 @@ import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.containingClass import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.isAncestor +import org.jetbrains.kotlin.utils.addToStdlib.safeAs fun KtExpression.asProperty(): KtProperty? = (this as? KtNameReferenceExpression) - ?.references - ?.firstOrNull { it is KtSimpleNameReference } + ?.mainReference ?.resolve() as? KtProperty fun KtExpression.unpackedReferenceToProperty(): KtProperty? = @@ -42,6 +43,37 @@ fun KtElement.topLevelContainingClassOrObject(): KtClassOrObject? = }.lastOrNull() fun KtReferenceExpression.resolve() = - references - .firstOrNull { it is KtSimpleNameReference } - ?.resolve() \ No newline at end of file + mainReference.resolve() + +fun KtPsiFactory.createGetter(body: KtExpression?): KtPropertyAccessor { + val property = + createProperty("val x get" + if (body == null) "" else if (body is KtBlockExpression) "() { return 1 }" else "() = 1") + val getter = property.getter!! + val bodyExpression = getter.bodyExpression + + bodyExpression?.replace(body!!) + return getter +} + +fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String): KtPropertyAccessor { + val property = when (body) { + null -> createProperty("var x = 1\n get() = 1\n set") + is KtBlockExpression -> createProperty("var x get() = 1\nset($fieldName) {\n field = $fieldName\n }") + else -> createProperty("var x get() = 1\nset($fieldName) = TODO()") + } + val setter = property.setter!! + if (body != null) { + setter.bodyExpression?.replace(body) + } + return setter +} + +fun KtClassOrObject.parentClassForCompanionOrThis(): KtClassOrObject = + if (safeAs()?.isCompanion() == true) + getStrictParentOfType() ?: this + else this + +fun KtElement.hasUsagesOutsideOf(inElement: KtElement, outsideElements: List): Boolean = + ReferencesSearch.search(this, LocalSearchScope(inElement)).any { reference -> + outsideElements.none { it.isAncestor(reference.element) } + }