New J2K: rewrite getters and setters conversion in post-processing

KT-31700 fixed
This commit is contained in:
Ilya Kirillov
2019-05-29 14:16:44 +03:00
parent 63434fc46c
commit 621ae80f1d
11 changed files with 809 additions and 465 deletions
@@ -1,428 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.nj2k.postProcessing
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
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.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.nj2k.parentOfType
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 {
override val writeActionNeeded: Boolean = true
data class PropertyData(
var name: String,
var getter: Accessor? = null,
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 ktFunction: KtFunction,
val isPure: Boolean,
val isFromCurrentDeclaration: Boolean
)
private fun KtExpression.statements() =
if (this is KtBlockExpression) statements
else listOf(this)
private fun KtFunction.getGetterTarget(): KtProperty? =
collectDescendantsOfType<KtReturnExpression>()
.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 = getGetterTarget()
val isPure = target != null
&& bodyExpression?.statements()?.size == 1
return Accessor(
name,
target,
this,
isPure,
isFromCurrentDeclaration = containingClassOrObject == klass
)
}
private inline fun <reified D : MemberDescriptor, reified P : KtElement> KtClassOrObject.declarations(): List<P> =
(analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, this] as ClassDescriptor)
.unsubstitutedMemberScope.getContributedDescriptors { true }
.filterIsInstance<D>()
.mapNotNull { it.findPsi() as? P }
private fun KtFunction.isProcedure() =
bodyExpression is KtBlockExpression? && !hasDeclaredReturnType()
|| getReturnTypeReference()?.typeElement?.text == "Unit"
private fun KtFunction.asSetter(klass: KtClassOrObject): Accessor? {
if (typeParameters.isNotEmpty()) return null
if (valueParameters.size != 1) return null
if (!isProcedure()) return null
val name = setterName() ?: return null
val target = bodyExpression
?.statements()
?.singleOrNull()
?.let {
if (it is KtBinaryExpression) {
if (it.operationToken != KtTokens.EQ) return@let null
val right = it.right as? KtNameReferenceExpression ?: 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,
isPure = target != null || bodyExpression?.statements()?.isEmpty() == true,
isFromCurrentDeclaration = containingClassOrObject == klass
)
}
private fun KtFunction.getterName() =
name?.takeIf { JvmAbi.isGetterName(it) }
?.removePrefix("get")
?.takeIf {
it.isNotEmpty() && it.first().isUpperCase()
|| it.startsWith("is") && it.length > 2 && it[2].isUpperCase()
}?.decapitalize()
?.escaped()
private fun KtFunction.setterName() =
name?.takeIf { JvmAbi.isSetterName(it) }
?.removePrefix("set")
?.takeIf { it.isNotEmpty() && it.first().isUpperCase() }
?.decapitalize()
?.escaped()
private fun KtClassOrObject.collectGetterAndSettersPairs(): Sequence<PropertyData> {
val properties = mutableMapOf<String, PropertyData>()
val functions = declarations<SimpleFunctionDescriptor, KtFunction>()
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.asSequence()
}
private fun List<KtModifierListOwner>.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<PropertyDataExtended> {
val propertyByName = klass
.declarations<PropertyDescriptor, KtProperty>()
.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<KtClassOrObject>()
is KtReferenceExpression -> resolve() == parentOfType<KtClassOrObject>()
else -> false
}
private fun <T : KtExpression> 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.isReferenceToThis() ->
parent
else -> reference
}
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.isPure && getterFunction.name == getterFunction.target?.name) null
else {
getterFunction.ktFunction.bodyExpression
?.withReplacedExpressionInBody(target, factory.createExpression("field"), replaceOnlyWriteUsages = false)
}
add(factory.createGetter(newBody))
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 KtProperty.addSetter(setterInfo: Accessor): KtPropertyAccessor {
val factory = KtPsiFactory(this)
val newBody =
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!!
}
private fun KtElement.forAllUsages(action: (KtElement) -> Unit) {
ReferencesSearch.search(this, LocalSearchScope(containingKtFile))
.forEach { action(it.element as KtElement) }
}
private fun KtElement.usages() =
ReferencesSearch.search(this, LocalSearchScope(containingKtFile))
private fun KtProperty.addGetterAndChangeAllUsages(getter: Accessor, name: String) =
addGetter(getter, this).also {
val factory = KtPsiFactory(this)
getter.ktFunction.forAllUsages { usage ->
usage.parentOfType<KtCallExpression>()!!.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<KtCallExpression>()!!
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: PsiElement, diagnostics: Diagnostics): (() -> Unit)? {
if (element !is KtClassOrObject) return null
return {
val factory = KtPsiFactory(element)
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)
usage.replace(expression)
}
target.setName(name)
}
target.addGetterAndChangeAllUsages(getterAccessor, name).also { getter ->
getter.setVisibility(visibility)
}
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())
}
if (target.parent?.isPhysical != true) {
when {
getterAccessor.isFromCurrentDeclaration ->
element.addDeclarationAfter(target, getterAccessor.ktFunction)
setterAccessor?.isFromCurrentDeclaration == true ->
element.addDeclarationAfter(target, setterAccessor.ktFunction)
}
}
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)
}
}
}
}
}
@@ -0,0 +1,618 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.nj2k.postProcessing.processings
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.setVisibility
import org.jetbrains.kotlin.idea.refactoring.isAbstract
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.readWriteAccess
import org.jetbrains.kotlin.idea.util.CommentSaver
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.nj2k.*
import org.jetbrains.kotlin.nj2k.postProcessing.*
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.util.isJavaDescriptor
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.mapToIndex
class ConvertGettersAndSettersToPropertyProcessing : ElementsBasedPostProcessing() {
private fun KtNamedFunction.hasOverrides(): Boolean =
toLightMethods().singleOrNull()?.let { lightMethod ->
OverridingMethodsSearch.search(lightMethod).findFirst()
} != null
private fun KtNamedFunction.hasSuperFunction(): Boolean =
resolveToDescriptorIfAny()?.original?.overriddenDescriptors?.isNotEmpty() == true
private fun KtNamedFunction.hasInvalidSuperDescriptors(): Boolean =
resolveToDescriptorIfAny()
?.original
?.overriddenDescriptors
?.any { it.isJavaDescriptor || it is FunctionDescriptor } == true
private fun KtExpression.statements() =
if (this is KtBlockExpression) statements
else listOf(this)
private fun KtNamedFunction.asGetter(): Getter? {
val name = name?.asGetterName() ?: return null
if (valueParameters.isNotEmpty()) return null
if (typeParameters.isNotEmpty()) return null
val target = bodyExpression
?.statements()
?.singleOrNull()
?.safeAs<KtReturnExpression>()
?.returnedExpression
?.unpackedReferenceToProperty()
?.takeIf {
it.type() == this.type()!!
}
return RealGetter(this, target, name)
}
private fun KtNamedFunction.asSetter(): Setter? {
val name = name?.asSetterName() ?: return null
if (typeParameters.isNotEmpty()) return null
if (valueParameters.size != 1) return null
val descriptor = resolveToDescriptorIfAny() ?: return null
if (descriptor.returnType?.isUnit() != true) return null
val target = bodyExpression
?.statements()
?.singleOrNull()
?.let { expression ->
if (expression is KtBinaryExpression) {
if (expression.operationToken != KtTokens.EQ) return@let null
val right = expression.right as? KtNameReferenceExpression ?: return@let null
if (right.resolve() != valueParameters.single()) return@let null
expression.left?.unpackedReferenceToProperty()
} else null
}?.takeIf {
it.type() == valueParameters.single().type()
}
return RealSetter(this, target, name)
}
private fun KtDeclaration.asPropertyAccessor(): PropertyInfo? {
return when (this) {
is KtProperty -> RealProperty(this, name ?: return null)
is KtNamedFunction -> asGetter() ?: asSetter()
else -> null
}
}
private fun KtElement.forAllUsages(action: (KtElement) -> Unit) {
usages().forEach { action(it.element as KtElement) }
}
private fun addGetter(
getter: Getter,
property: KtProperty,
factory: KtPsiFactory,
isFakeProperty: Boolean
): KtPropertyAccessor {
val body =
if (isFakeProperty) getter.body
else getter.body?.withReplacedExpressionInBody(
property,
factory.createExpression(KtTokens.FIELD_KEYWORD.value),
replaceOnlyWriteUsages = false
)
val ktGetter = factory.createGetter(body, getter.modifiersText)
ktGetter.filterModifiers()
return property.add(ktGetter).cast<KtPropertyAccessor>().let {
if (getter is RealGetter) {
getter.function.forAllUsages { usage ->
usage.parentOfType<KtCallExpression>()!!.replace(factory.createExpression(getter.name))
}
}
it
}
}
private fun KtParameter.rename(newName: String) {
val renamer = RenamePsiElementProcessor.forElement(this)
val usageInfos =
renamer.findReferences(this, false).mapNotNull { reference ->
val element = reference.element
val isBackingField = element is KtNameReferenceExpression &&
element.text == KtTokens.FIELD_KEYWORD.value
&& element.mainReference.resolve() == this
&& isAncestor(element)
if (isBackingField) return@mapNotNull null
renamer.createUsageInfo(this, reference, reference.element)
}.toTypedArray()
renamer.renameElement(this, newName, usageInfos, null)
}
private fun createSetter(
setter: Setter,
property: KtProperty,
factory: KtPsiFactory,
isFakeProperty: Boolean
): KtPropertyAccessor {
if (setter is RealSetter) {
setter.function.valueParameters.single().rename(setter.parameterName)
}
val body =
if (isFakeProperty) setter.body
else setter.body?.withReplacedExpressionInBody(
property,
factory.createExpression(KtTokens.FIELD_KEYWORD.value),
true
)
val ktSetter = factory.createSetter(
body,
setter.parameterName,
setter.modifiersText?.takeIf { it.isNotEmpty() } ?: property.visibilityModifierTypeOrDefault().value
)
ktSetter.filterModifiers()
if (setter is RealSetter) {
setter.function.forAllUsages { usage ->
val callExpression = usage.parentOfType<KtCallExpression>()!!
val qualifier = callExpression.getQualifiedExpressionForSelector()
val newValue = callExpression.valueArguments.single()
if (qualifier != null) {
qualifier.replace(
factory.createExpression("${qualifier.receiverExpression.text}.${setter.name} = ${newValue.text}")
)
} else {
callExpression.replace(factory.createExpression("${setter.name} = ${newValue.text}"))
}
}
}
return ktSetter
}
private fun KtPropertyAccessor.filterModifiers() {
removeModifier(KtTokens.OVERRIDE_KEYWORD)
removeModifier(KtTokens.FINAL_KEYWORD)
removeModifier(KtTokens.OPEN_KEYWORD)
}
private fun KtExpression.isReferenceToThis() =
when (this) {
is KtThisExpression -> instanceReference.resolve() == parentOfType<KtClassOrObject>()
is KtReferenceExpression -> resolve() == parentOfType<KtClassOrObject>()
else -> false
}
private fun KtElement.usages() =
ReferencesSearch.search(this, LocalSearchScope(containingKtFile))
private fun <T : KtExpression> T.withReplacedExpressionInBody(
from: KtElement,
to: KtExpression,
replaceOnlyWriteUsages: Boolean
): T = also {
from.usages()
.asSequence()
.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
}
if (!replaceOnlyWriteUsages
|| (referenceExpression.parent as? KtExpression)?.asAssignment()?.left == referenceExpression
) {
referenceExpression.replace(to)
}
}
}
private fun List<KtClassOrObject>.sortedByInheritance(): List<KtClassOrObject> {
fun ClassDescriptor.superClassAndSuperInterfaces() =
getSuperInterfaces() + listOfNotNull(getSuperClassNotAny())
val sorted = mutableListOf<KtClassOrObject>()
val visited = Array(size) { false }
val descriptors = map { it.resolveToDescriptorIfAny()!! }
val descriptorToIndex = descriptors.mapToIndex()
val outers = descriptors.map { descriptor ->
descriptor.superClassAndSuperInterfaces().mapNotNull { descriptorToIndex[it] }
}
fun dfs(current: Int) {
visited[current] = true
for (outer in outers[current]) {
if (!visited[outer]) {
dfs(outer)
}
}
sorted.add(get(current))
}
for (index in descriptors.indices) {
if (!visited[index]) {
dfs(index)
}
}
return sorted
}
override fun runProcessing(elements: List<PsiElement>, converterContext: NewJ2kConverterContext) {
val classes = elements.descendantsOfType<KtClassOrObject>().sortedByInheritance()
for (klass in classes) {
convertClass(klass)
}
}
private fun causesNameConflictInCurrentDeclarationAndItsParents(name: String, declaration: DeclarationDescriptor?): Boolean =
when (declaration) {
is ClassDescriptor -> {
declaration.unsubstitutedMemberScope.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES) {
it.asString() == name
}.isNotEmpty()
|| causesNameConflictInCurrentDeclarationAndItsParents(name, declaration.containingDeclaration)
}
else -> false
}
private fun KtClassOrObject.collectGettersAndSetters(factory: KtPsiFactory): List<PropertyWithAccessors> {
val classDescriptor = resolveToDescriptorIfAny() ?: return emptyList()
val variablesDescriptorsMap =
(listOfNotNull(classDescriptor.getSuperClassNotAny()) + classDescriptor.getSuperInterfaces())
.flatMap { superClass ->
superClass
.unsubstitutedMemberScope
.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES)
}
.asSequence()
.filterIsInstance<VariableDescriptor>()
.associateBy { it.name.asString() }
return declarations
.asSequence()
.mapNotNull { it.asPropertyAccessor() }
.groupBy { it.name.removePrefix("is").decapitalize() }
.values
.mapNotNull { group ->
val realGetter = group.firstIsInstanceOrNull<RealGetter>()
val realSetter = group.firstIsInstanceOrNull<RealSetter>()?.takeIf { setter ->
if (realGetter == null) return@takeIf true
if (setter.function.valueParameters.first().type()?.makeNotNullable() !=
realGetter.function.type()?.makeNotNullable()
) {
if (isInterfaceClass()) return@mapNotNull null
false
} else true
}
val realProperty = group.firstIsInstanceOrNull<RealProperty>()
if (realGetter == null && realSetter == null) return@mapNotNull null
val name = realGetter?.name ?: realSetter!!.name
val type =
realGetter?.function?.typeReference?.text
?: realSetter?.function?.valueParameters?.first()?.typeReference?.text!!
if (realSetter != null
&& realGetter != null
&& isInterfaceClass()
&& realSetter.function.hasOverrides() != realGetter.function.hasOverrides()
) return@mapNotNull null
if (realSetter != null
&& realGetter != null
&& realSetter.function.hasSuperFunction() != realGetter.function.hasSuperFunction()
) return@mapNotNull null
if (realProperty != null
&& realGetter?.target != null
&& realGetter.target != realProperty.property
&& realProperty.property.hasInitializer()
) return@mapNotNull null
if (realGetter?.function?.hasInvalidSuperDescriptors() == true
|| realSetter?.function?.hasInvalidSuperDescriptors() == true
) return@mapNotNull null
if (realProperty == null) {
if (causesNameConflictInCurrentDeclarationAndItsParents(
name,
classDescriptor.containingDeclaration
)
) return@mapNotNull null
}
if (realProperty != null && (realGetter != null && realGetter.target == null || realSetter != null && realSetter.target == null)) {
if (!realProperty.property.isPrivate()) return@mapNotNull null
val hasUsages =
realProperty.property.hasUsagesOutsideOf(
containingKtFile,
listOfNotNull(realGetter?.function, realSetter?.function)
)
if (hasUsages) return@mapNotNull null
}
if (realSetter != null && realProperty != null) {
val assignFieldOfOtherInstance = realProperty.property.usages().any { usage ->
val element = usage.safeAs<KtSimpleNameReference>()?.element ?: return@any false
if (!element.readWriteAccess(useResolveForReadWrite = true).isWrite) return@any false
val parent = element.parent
parent is KtQualifiedExpression && !parent.receiverExpression.isReferenceToThis()
}
if (assignFieldOfOtherInstance) return@mapNotNull null
}
if (realGetter != null && realProperty != null) {
val getFieldOfOtherInstanceInGetter = realProperty.property.usages().any { usage ->
val element = usage.safeAs<KtSimpleNameReference>()?.element ?: return@any false
val parent = element.parent
parent is KtQualifiedExpression
&& !parent.receiverExpression.isReferenceToThis()
&& realGetter.function.isAncestor(element)
}
if (getFieldOfOtherInstanceInGetter) return@mapNotNull null
}
val getter = realGetter ?: when {
realProperty?.property?.resolveToDescriptorIfAny()?.overriddenDescriptors?.any {
it.safeAs<VariableDescriptor>()?.isVar == true
} == true -> FakeGetter(name, null, "")
variablesDescriptorsMap[name]?.let { variable ->
variable.isVar && variable.containingDeclaration != classDescriptor
} == true ->
FakeGetter(name, factory.createExpression("super.$name"), "")
else -> return@mapNotNull null
}
val mergedProperty =
if (getter is RealGetter
&& getter.target != null
&& getter.target!!.name != getter.name
&& (realSetter == null || realSetter.target != null)
) {
MergedProperty(name, type, realSetter != null, getter.target!!)
} else null
val setter = realSetter ?: when {
realProperty?.property?.isVar == true ->
FakeSetter(name, null, "")
realProperty?.property?.resolveToDescriptorIfAny()?.overriddenDescriptors?.any {
it.safeAs<VariableDescriptor>()?.isVar == true
} == true
|| variablesDescriptorsMap[name]?.isVar == true ->
FakeSetter(
name,
factory.createBlock("super.$name = $name"),
""
)
realGetter != null
&& (realProperty != null
&& realProperty.property.visibilityModifierTypeOrDefault() != realGetter.function.visibilityModifierTypeOrDefault()
&& realProperty.property.isVar
|| mergedProperty != null
&& mergedProperty.mergeTo.visibilityModifierTypeOrDefault() != realGetter.function.visibilityModifierTypeOrDefault()
&& mergedProperty.mergeTo.isVar
) ->
FakeSetter(name, null, null)
else -> null
}
val isVar = setter != null
val property = mergedProperty?.copy(isVar = isVar)
?: realProperty?.copy(isVar = isVar)
?: FakeProperty(name, type, isVar)
PropertyWithAccessors(
property,
getter,
setter
)
}
}
private fun KtProperty.renameTo(newName: String, factory: KtPsiFactory) {
for (usage in usages().toList()) {
val element = usage.element
val isBackingField = element is KtNameReferenceExpression
&& element.text == KtTokens.FIELD_KEYWORD.value
&& element.mainReference.resolve() == this
&& isAncestor(element)
if (isBackingField) continue
val replacer =
if (element.parent is KtQualifiedExpression) factory.createExpression(newName)
else factory.createExpression("this.$newName")
element.replace(replacer)
}
setName(newName)
}
private fun convertClass(klass: KtClassOrObject) {
val factory = KtPsiFactory(klass)
val accessors = klass.collectGettersAndSetters(factory)
for ((property, getter, setter) in accessors) {
val ktProperty = when (property) {
is RealProperty -> {
if (property.property.isVar != property.isVar) {
property.property.valOrVarKeyword.replace(
if (property.isVar) factory.createVarKeyword() else factory.createValKeyword()
)
}
property.property
}
is FakeProperty -> factory.createProperty(property.name, property.type, property.isVar).let {
val anchor = getter.safeAs<RealAccessor>()?.function ?: setter.cast<RealAccessor>().function
klass.addDeclarationBefore(it, anchor)
}
is MergedProperty -> {
property.mergeTo
}
}
val isOpen = getter.safeAs<RealGetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true
|| setter.safeAs<RealSetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true
val ktGetter = addGetter(getter, ktProperty, factory, property.isFake)
val ktSetter =
setter?.let {
createSetter(it, ktProperty, factory, property.isFake)
}
val getterVisibility = getter.safeAs<RealGetter>()?.function?.visibilityModifierTypeOrDefault()
if (getter is RealGetter) {
if (getter.function.isAbstract()) {
ktProperty.addModifier(KtTokens.ABSTRACT_KEYWORD)
}
val commentSaver = CommentSaver(getter.function)
getter.function.delete()
commentSaver.restore(ktProperty)
}
if (setter is RealSetter) {
val commentSaver = CommentSaver(setter.function)
setter.function.delete()
commentSaver.restore(ktProperty)
}
val propertyVisibility = ktProperty.visibilityModifierTypeOrDefault()
getterVisibility?.let { ktProperty.setVisibility(it) }
if (ktSetter != null) {
if (setter !is RealSetter) {
ktSetter.setVisibility(propertyVisibility)
}
ktProperty.addAfter(ktSetter, ktGetter)
}
if (property is MergedProperty) {
ktProperty.renameTo(property.name, factory)
}
if (isOpen) {
ktProperty.addModifier(KtTokens.OPEN_KEYWORD)
}
}
}
}
private data class PropertyWithAccessors(
val property: Property,
val getter: Getter,
val setter: Setter?
)
private interface PropertyInfo {
val name: String
}
private interface Accessor : PropertyInfo {
val target: KtProperty?
val body: KtExpression?
val modifiersText: String?
val isPure: Boolean
}
private sealed class Property : PropertyInfo {
abstract val isVar: Boolean
}
private data class RealProperty(
val property: KtProperty,
override val name: String,
override val isVar: Boolean = property.isVar
) : Property()
private data class FakeProperty(override val name: String, val type: String, override val isVar: Boolean) : Property()
private data class MergedProperty(
override val name: String,
val type: String,
override val isVar: Boolean,
val mergeTo: KtProperty
) : Property()
private sealed class Getter : Accessor
private sealed class Setter : Accessor {
abstract val parameterName: String
}
private interface FakeAccessor : Accessor {
override val target: KtProperty?
get() = null
override val isPure: Boolean
get() = true
}
private interface RealAccessor : Accessor {
val function: KtNamedFunction
override val body: KtExpression?
get() = function.bodyExpression
override val modifiersText: String
get() = function.modifierList?.text.orEmpty()
override val isPure: Boolean
get() = target != null
}
private data class RealGetter(
override val function: KtNamedFunction,
override val target: KtProperty?,
override val name: String
) : Getter(), RealAccessor
private data class FakeGetter(
override val name: String,
override val body: KtExpression?,
override val modifiersText: String
) : Getter(), FakeAccessor
private data class RealSetter(
override val function: KtNamedFunction,
override val target: KtProperty?,
override val name: String
) : Setter(), RealAccessor {
override val parameterName: String
get() = (function.valueParameters.first().name ?: name).fixSetterParameterName()
}
private data class FakeSetter(
override val name: String,
override val body: KtExpression?,
override val modifiersText: String?
) : Setter(), FakeAccessor {
override val parameterName: String
get() = name.fixSetterParameterName()
}
private fun String.fixSetterParameterName() =
if (this == KtTokens.FIELD_KEYWORD.value) "value"
else this
private val PropertyInfo.isFake: Boolean
get() = this is FakeAccessor
@@ -5,25 +5,20 @@
package org.jetbrains.kotlin.nj2k.postProcessing
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
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.formatter.commitAndUnblockDocument
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
fun KtExpression.asProperty(): KtProperty? =
(this as? KtNameReferenceExpression)
?.mainReference
?.resolve() as? KtProperty
fun KtExpression.unpackedReferenceToProperty(): KtProperty? =
when (this) {
is KtDotQualifiedExpression ->
@@ -39,17 +34,20 @@ fun KtExpression.unpackedReferenceToProperty(): KtProperty? =
fun KtDeclaration.type() =
(resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType
fun KtElement.topLevelContainingClassOrObject(): KtClassOrObject? =
generateSequence(getStrictParentOfType<KtClassOrObject>()) {
it.getStrictParentOfType()
}.lastOrNull()
fun KtReferenceExpression.resolve() =
mainReference.resolve()
fun KtPsiFactory.createGetter(body: KtExpression?): KtPropertyAccessor {
fun KtPsiFactory.createGetter(body: KtExpression?, modifiers: String?): KtPropertyAccessor {
val property =
createProperty("val x get" + if (body == null) "" else if (body is KtBlockExpression) "() { return 1 }" else "() = 1")
createProperty(
"val x\n ${modifiers.orEmpty()} get" +
when (body) {
is KtBlockExpression -> "() { return 1 }"
null -> ""
else -> "() = 1"
} + "\n"
)
val getter = property.getter!!
val bodyExpression = getter.bodyExpression
@@ -57,11 +55,12 @@ fun KtPsiFactory.createGetter(body: KtExpression?): KtPropertyAccessor {
return getter
}
fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String): KtPropertyAccessor {
fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String?, modifiers: String?): KtPropertyAccessor {
val modifiersText = modifiers.orEmpty()
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()")
null -> createProperty("var x = 1\n get() = 1\n $modifiersText set")
is KtBlockExpression -> createProperty("var x get() = 1\n $modifiersText set($fieldName) {\n field = $fieldName\n }")
else -> createProperty("var x get() = 1\n $modifiersText set($fieldName) = TODO()")
}
val setter = property.setter!!
if (body != null) {
@@ -70,18 +69,20 @@ fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String): KtPropert
return setter
}
fun KtClassOrObject.parentClassForCompanionOrThis(): KtClassOrObject =
if (safeAs<KtObjectDeclaration>()?.isCompanion() == true)
getStrictParentOfType() ?: this
else this
fun KtElement.hasUsagesOutsideOf(inElement: KtElement, outsideElements: List<KtElement>): Boolean =
ReferencesSearch.search(this, LocalSearchScope(inElement)).any { reference ->
outsideElements.none { it.isAncestor(reference.element) }
}
fun String.escaped() =
if (this in keywords || '$' in this) "`$this`"
else this
private val keywords = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet()
//hack until KT-30804 is fixed
fun KtModifierListOwner.removeModifierSmart(modifierToken: KtModifierKeywordToken) {
val newElement = copy() as KtModifierListOwner
newElement.removeModifier(modifierToken)
replace(newElement)
containingFile.commitAndUnblockDocument()
}
inline fun <reified T : PsiElement> List<PsiElement>.descendantsOfType(): List<T> =
flatMap { it.collectDescendantsOfType() }
@@ -17,8 +17,6 @@
package org.jetbrains.kotlin.nj2k
import org.jetbrains.kotlin.j2k.ast.Nullability
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.NewCodeBuilder.ParenthesisKind.*
import org.jetbrains.kotlin.nj2k.conversions.parentOfType
@@ -997,13 +995,6 @@ private fun JKDelegationConstructorCall.isCallOfConstructorOf(type: JKType): Boo
}
}
private val KEYWORDS = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet()
private fun String.escaped() =
if (this in KEYWORDS || '$' in this) "`$this`"
else this
private val mappedToKotlinFqNames =
setOf(
"java.util.ArrayList",
@@ -15,6 +15,10 @@ import org.jetbrains.kotlin.nj2k.tree.impl.psi
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class DefaultArgumentsConversion(private val context: NewJ2kConverterContext) : RecursiveApplicableConversionBase() {
private fun JKMethod.canBeGetterOrSetter() =
name.value.asGetterName() != null
|| name.value.asSetterName() != null
private fun JKMethod.canNotBeMerged(): Boolean =
modality == Modality.ABSTRACT
@@ -23,6 +27,7 @@ class DefaultArgumentsConversion(private val context: NewJ2kConverterContext) :
|| hasExtraModifier(ExtraModifier.SYNCHRONIZED)
|| context.converter.converterServices.oldServices.referenceSearcher.hasOverrides(psi()!!)
|| annotationList.annotations.isNotEmpty()
|| canBeGetterOrSetter()
override fun applyToElement(element: JKTreeElement): JKTreeElement {
@@ -8,6 +8,10 @@ package org.jetbrains.kotlin.nj2k
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.parentsOfType
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtFunction
fun <T> List<T>.replace(element: T, replacer: T): List<T> {
val mutableList = toMutableList()
@@ -21,3 +25,24 @@ inline fun <reified T : PsiElement> PsiElement.parentOfType(): T? =
inline fun <reified T : PsiElement> PsiElement.parentsOfType(): Sequence<T> = parentsOfType(T::class.java)
fun String.asGetterName() =
takeIf { JvmAbi.isGetterName(it) }
?.removePrefix("get")
?.takeIf {
it.isNotEmpty() && it.first().isUpperCase()
|| it.startsWith("is") && it.length > 2 && it[2].isUpperCase()
}?.decapitalize()
?.escaped()
fun String.asSetterName() =
takeIf { JvmAbi.isSetterName(it) }
?.removePrefix("set")
?.takeIf { it.isNotEmpty() && it.first().isUpperCase() }
?.decapitalize()
?.escaped()
private val KEYWORDS = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet()
fun String.escaped() =
if (this in KEYWORDS || '$' in this) "`$this`"
else this
@@ -0,0 +1,62 @@
interface I {
boolean isSomething1();
Boolean isSomething2();
int isSomething3();
boolean isSomething4();
void setSomething4(boolean value);
boolean isSomething5();
void setSomething5(boolean value);
boolean getSomething6();
void setSomething6(boolean value);
}
abstract class A extends B implements I {
@Override
public boolean isSomething1() {
return true;
}
public void setSomething1(boolean b) {
}
@Override
public boolean isSomething4() {
return false;
}
@Override
public void setSomething5(boolean value) {
}
@Override
public void setSomething6(boolean value) {
}
}
abstract class B extends A implements I {
@Override
public boolean isSomething1() {
return true;
}
public void setSomething1(boolean b) {
}
@Override
public boolean isSomething4() {
return false;
}
@Override
public void setSomething5(boolean value) {
}
@Override
public void setSomething6(boolean value) {
}
}
@@ -0,0 +1,39 @@
// ERROR: There's a cycle in the inheritance hierarchy for this type
// ERROR: There's a cycle in the inheritance hierarchy for this type
internal interface I {
val isSomething1: Boolean
val isSomething2: Boolean?
val isSomething3: Int
fun isSomething4(): Boolean
fun setSomething4(value: Boolean)
fun isSomething5(): Boolean
fun setSomething5(value: Boolean)
fun getSomething6(): Boolean
fun setSomething6(value: Boolean)
}
internal abstract class A : B(), I {
override var isSomething1: Boolean
get() = true
set(b) {}
override fun isSomething4(): Boolean {
return false
}
override fun setSomething5(value: Boolean) {}
override fun setSomething6(value: Boolean) {}
}
internal abstract class B : A(), I {
override var isSomething1: Boolean
get() = true
set(b) {}
override fun isSomething4(): Boolean {
return false
}
override fun setSomething5(value: Boolean) {}
override fun setSomething6(value: Boolean) {}
}
+12
View File
@@ -0,0 +1,12 @@
//Interface
public interface Parent {
public void getX();
}
//Subclass
public class Child implements Parent {
@Override
public void getX() {
}
}
+9
View File
@@ -0,0 +1,9 @@
//Interface
interface Parent {
val x: Unit
}
//Subclass
class Child : Parent {
override val x: Unit get() {}
}
@@ -1376,6 +1376,11 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
runTest("nj2k/testData/newJ2k/detectProperties/Comments.java");
}
@TestMetadata("CyclicInheritance.java")
public void testCyclicInheritance() throws Exception {
runTest("nj2k/testData/newJ2k/detectProperties/CyclicInheritance.java");
}
@TestMetadata("DataClass.java")
public void testDataClass() throws Exception {
runTest("nj2k/testData/newJ2k/detectProperties/DataClass.java");
@@ -1511,6 +1516,11 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
runTest("nj2k/testData/newJ2k/detectProperties/KeywordPropertyName.java");
}
@TestMetadata("kt-31700.java")
public void testKt_31700() throws Exception {
runTest("nj2k/testData/newJ2k/detectProperties/kt-31700.java");
}
@TestMetadata("Overrides.java")
public void ignoreTestOverrides() throws Exception {
runTest("nj2k/testData/newJ2k/detectProperties/Overrides.java");