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 deleted file mode 100644 index 775ad0a0aff..00000000000 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/ConvertGettersAndSetters.kt +++ /dev/null @@ -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() - .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 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" - - 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 { - val properties = mutableMapOf() - val functions = declarations() - - 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.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.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()!!.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: 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) - } - } - } - } -} \ No newline at end of file diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/processings/ConvertGettersAndSettersToPropertyProcessing.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/processings/ConvertGettersAndSettersToPropertyProcessing.kt new file mode 100644 index 00000000000..07e790c4b06 --- /dev/null +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/processings/ConvertGettersAndSettersToPropertyProcessing.kt @@ -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() + ?.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().let { + if (getter is RealGetter) { + getter.function.forAllUsages { usage -> + usage.parentOfType()!!.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()!! + 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() + is KtReferenceExpression -> resolve() == parentOfType() + else -> false + } + + private fun KtElement.usages() = + ReferencesSearch.search(this, LocalSearchScope(containingKtFile)) + + private fun 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.sortedByInheritance(): List { + fun ClassDescriptor.superClassAndSuperInterfaces() = + getSuperInterfaces() + listOfNotNull(getSuperClassNotAny()) + + val sorted = mutableListOf() + 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, converterContext: NewJ2kConverterContext) { + val classes = elements.descendantsOfType().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 { + val classDescriptor = resolveToDescriptorIfAny() ?: return emptyList() + + val variablesDescriptorsMap = + (listOfNotNull(classDescriptor.getSuperClassNotAny()) + classDescriptor.getSuperInterfaces()) + .flatMap { superClass -> + superClass + .unsubstitutedMemberScope + .getDescriptorsFiltered(DescriptorKindFilter.VARIABLES) + } + .asSequence() + .filterIsInstance() + .associateBy { it.name.asString() } + + return declarations + .asSequence() + .mapNotNull { it.asPropertyAccessor() } + .groupBy { it.name.removePrefix("is").decapitalize() } + .values + .mapNotNull { group -> + val realGetter = group.firstIsInstanceOrNull() + val realSetter = group.firstIsInstanceOrNull()?.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() + 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()?.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()?.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()?.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()?.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()?.function ?: setter.cast().function + klass.addDeclarationBefore(it, anchor) + } + is MergedProperty -> { + property.mergeTo + } + } + + val isOpen = getter.safeAs()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true + || setter.safeAs()?.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()?.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 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 1fab2ec711d..a71c304c4ae 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 @@ -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()) { - 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()?.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) } } -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 List.descendantsOfType(): List = + flatMap { it.collectDescendantsOfType() } \ No newline at end of file diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/NewCodeBuilder.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/NewCodeBuilder.kt index 5caf0231b8d..321ac3ac4db 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/NewCodeBuilder.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/NewCodeBuilder.kt @@ -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", diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt index 555558de404..2354276df48 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/DefaultArgumentsConversion.kt @@ -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 { diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/utils.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/utils.kt index c0c0f37920d..551d7ff5de2 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/utils.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/utils.kt @@ -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 List.replace(element: T, replacer: T): List { val mutableList = toMutableList() @@ -21,3 +25,24 @@ inline fun PsiElement.parentOfType(): T? = inline fun PsiElement.parentsOfType(): Sequence = 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 \ No newline at end of file diff --git a/nj2k/testData/newJ2k/detectProperties/CyclicInheritance.java b/nj2k/testData/newJ2k/detectProperties/CyclicInheritance.java new file mode 100644 index 00000000000..88e902115e6 --- /dev/null +++ b/nj2k/testData/newJ2k/detectProperties/CyclicInheritance.java @@ -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) { + } +} \ No newline at end of file diff --git a/nj2k/testData/newJ2k/detectProperties/CyclicInheritance.kt b/nj2k/testData/newJ2k/detectProperties/CyclicInheritance.kt new file mode 100644 index 00000000000..6df31715afe --- /dev/null +++ b/nj2k/testData/newJ2k/detectProperties/CyclicInheritance.kt @@ -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) {} +} \ No newline at end of file diff --git a/nj2k/testData/newJ2k/detectProperties/kt-31700.java b/nj2k/testData/newJ2k/detectProperties/kt-31700.java new file mode 100644 index 00000000000..f49e1c0a76d --- /dev/null +++ b/nj2k/testData/newJ2k/detectProperties/kt-31700.java @@ -0,0 +1,12 @@ +//Interface +public interface Parent { + public void getX(); +} + +//Subclass +public class Child implements Parent { + @Override + public void getX() { + + } +} \ No newline at end of file diff --git a/nj2k/testData/newJ2k/detectProperties/kt-31700.kt b/nj2k/testData/newJ2k/detectProperties/kt-31700.kt new file mode 100644 index 00000000000..694c0db4b04 --- /dev/null +++ b/nj2k/testData/newJ2k/detectProperties/kt-31700.kt @@ -0,0 +1,9 @@ +//Interface +interface Parent { + val x: Unit +} + +//Subclass +class Child : Parent { + override val x: Unit get() {} +} \ No newline at end of file diff --git a/nj2k/tests/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java b/nj2k/tests/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java index fec42dd1c26..691474add09 100644 --- a/nj2k/tests/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java +++ b/nj2k/tests/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java @@ -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");