New J2K: consider literal expressions in data class conversion & fix names conflict
#KT-32253 fixed
This commit is contained in:
+111
-67
@@ -11,19 +11,15 @@ import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
|
||||
import org.jetbrains.kotlin.idea.core.setVisibility
|
||||
import org.jetbrains.kotlin.idea.intentions.addUseSiteTarget
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.util.CommentSaver
|
||||
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
|
||||
import org.jetbrains.kotlin.nj2k.escaped
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.ElementsBasedPostProcessing
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.descendantsOfType
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.type
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.unpackedReferenceToProperty
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.asAssignment
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClass
|
||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class ConvertToDataClassProcessing : ElementsBasedPostProcessing() {
|
||||
private fun KtCallableDeclaration.rename(newName: String) {
|
||||
@@ -35,43 +31,52 @@ class ConvertToDataClassProcessing : ElementsBasedPostProcessing() {
|
||||
setName(escapedName)
|
||||
}
|
||||
|
||||
private data class DataClassInfo(
|
||||
val constructorParameter: KtParameter,
|
||||
val property: KtProperty,
|
||||
val initBlockStatement: KtBinaryExpression
|
||||
)
|
||||
|
||||
private fun collectPropertiesData(klass: KtClass): List<DataClassInfo> =
|
||||
klass.getAnonymousInitializers()
|
||||
.flatMap { (it.body as KtBlockExpression).statements }
|
||||
.asSequence()
|
||||
.mapNotNull { it.asAssignment() }
|
||||
.mapNotNull { statement ->
|
||||
val property =
|
||||
statement.left
|
||||
?.unpackedReferenceToProperty()
|
||||
?.takeIf { it.containingClass() == klass } ?: return@mapNotNull null
|
||||
if (property.getter != null || property.setter != null) return@mapNotNull null
|
||||
if (property.initializer != null) return@mapNotNull null
|
||||
val constructorParameter =
|
||||
((statement.right as? KtReferenceExpression)
|
||||
?.references
|
||||
?.firstOrNull { it is KtSimpleNameReference }
|
||||
?.resolve() as? KtParameter)
|
||||
?.takeIf {
|
||||
it.containingClass() == klass && !it.hasValOrVar()
|
||||
} ?: return@mapNotNull null
|
||||
val constructorParameterType = constructorParameter.type() ?: return@mapNotNull null
|
||||
val propertyType = property.type() ?: return@mapNotNull null
|
||||
private fun collectInitializations(klass: KtClass): List<Initialization<*>> {
|
||||
val parametersUsed = mutableSetOf<KtParameter>()
|
||||
val propertyUsed = mutableSetOf<KtProperty>()
|
||||
@Suppress("UNCHECKED_CAST") return klass.getAnonymousInitializers()
|
||||
.singleOrNull()
|
||||
?.body?.safeAs<KtBlockExpression>()
|
||||
?.statements
|
||||
?.asSequence()
|
||||
?.map { statement ->
|
||||
val assignment = statement.asAssignment() ?: return@map null
|
||||
val property = assignment.left
|
||||
?.unpackedReferenceToProperty()
|
||||
?.takeIf { property ->
|
||||
property.containingClass() == klass
|
||||
&& property.initializer == null
|
||||
&& property !in propertyUsed
|
||||
}
|
||||
?: return@map null
|
||||
propertyUsed += property
|
||||
|
||||
if (constructorParameterType.makeNotNullable() != propertyType.makeNotNullable()) return@mapNotNull null
|
||||
|
||||
DataClassInfo(
|
||||
constructorParameter,
|
||||
property,
|
||||
statement
|
||||
)
|
||||
}.toList()
|
||||
when (val rightSide = assignment.right) {
|
||||
is KtReferenceExpression -> {
|
||||
val parameter = rightSide
|
||||
.resolve()
|
||||
?.safeAs<KtParameter>()
|
||||
?.takeIf { parameter ->
|
||||
parameter.containingClass() == klass
|
||||
&& !parameter.hasValOrVar()
|
||||
&& parameter !in parametersUsed
|
||||
} ?: return@map null
|
||||
val propertyType = property.type() ?: return@map null
|
||||
val parameterType = parameter.type() ?: return@map null
|
||||
if (propertyType != parameterType) return@map null
|
||||
parametersUsed += parameter
|
||||
ConstructorParameterInitialization(property, parameter, assignment)
|
||||
}
|
||||
is KtConstantExpression, is KtStringTemplateExpression -> {
|
||||
LiteralInitialization(property, rightSide, assignment)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}?.takeWhile { it != null }
|
||||
.orEmpty()
|
||||
.toList() as List<Initialization<*>>
|
||||
}
|
||||
|
||||
override fun runProcessing(elements: List<PsiElement>, converterContext: NewJ2kConverterContext) {
|
||||
for (klass in elements.descendantsOfType<KtClass>()) {
|
||||
@@ -79,39 +84,78 @@ class ConvertToDataClassProcessing : ElementsBasedPostProcessing() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertClass(klass: KtClass) {
|
||||
val factory = KtPsiFactory(klass)
|
||||
for ((constructorParameter, property, statement) in collectPropertiesData(klass)) {
|
||||
constructorParameter.addBefore(property.valOrVarKeyword, constructorParameter.nameIdentifier!!)
|
||||
constructorParameter.addAfter(factory.createWhiteSpace(), constructorParameter.valOrVarKeyword!!)
|
||||
constructorParameter.rename(property.name!!)
|
||||
val propertyCommentSaver = CommentSaver(property, saveLineBreaks = true)
|
||||
private fun ConstructorParameterInitialization.mergePropertyAndConstructorParameter() {
|
||||
val (property, constructorParameter, _) = this
|
||||
val factory = KtPsiFactory(property)
|
||||
constructorParameter.addBefore(property.valOrVarKeyword, constructorParameter.nameIdentifier!!)
|
||||
constructorParameter.addAfter(factory.createWhiteSpace(), constructorParameter.valOrVarKeyword!!)
|
||||
constructorParameter.rename(property.name!!)
|
||||
val propertyCommentSaver = CommentSaver(property, saveLineBreaks = true)
|
||||
|
||||
constructorParameter.setVisibility(property.visibilityModifierTypeOrDefault())
|
||||
for (annotationEntry in constructorParameter.annotationEntries) {
|
||||
if (annotationEntry.useSiteTarget == null) {
|
||||
annotationEntry.addUseSiteTarget(AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER, klass.project)
|
||||
}
|
||||
constructorParameter.setVisibility(property.visibilityModifierTypeOrDefault())
|
||||
for (annotationEntry in constructorParameter.annotationEntries) {
|
||||
if (annotationEntry.useSiteTarget == null) {
|
||||
annotationEntry.addUseSiteTarget(AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER, property.project)
|
||||
}
|
||||
|
||||
for (annotationEntry in property.annotationEntries) {
|
||||
constructorParameter.addAnnotationEntry(annotationEntry).also { entry ->
|
||||
if (entry.useSiteTarget == null) {
|
||||
entry.addUseSiteTarget(AnnotationUseSiteTarget.FIELD, klass.project)
|
||||
}
|
||||
}
|
||||
}
|
||||
property.delete()
|
||||
statement.delete()
|
||||
propertyCommentSaver.restore(constructorParameter, forceAdjustIndent = false)
|
||||
}
|
||||
|
||||
for (initBlock in klass.getAnonymousInitializers()) {
|
||||
for (annotationEntry in property.annotationEntries) {
|
||||
constructorParameter.addAnnotationEntry(annotationEntry).also { entry ->
|
||||
if (entry.useSiteTarget == null) {
|
||||
entry.addUseSiteTarget(AnnotationUseSiteTarget.FIELD, property.project)
|
||||
}
|
||||
}
|
||||
}
|
||||
property.delete()
|
||||
propertyCommentSaver.restore(constructorParameter, forceAdjustIndent = false)
|
||||
}
|
||||
|
||||
private fun KtClass.removeEmptyInitBlocks() {
|
||||
for (initBlock in getAnonymousInitializers()) {
|
||||
if ((initBlock.body as KtBlockExpression).statements.isEmpty()) {
|
||||
val commentSaver = CommentSaver(initBlock)
|
||||
initBlock.delete()
|
||||
klass.primaryConstructor?.let { commentSaver.restore(it) }
|
||||
primaryConstructor?.let { commentSaver.restore(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertClass(klass: KtClass) {
|
||||
for (initialization in collectInitializations(klass)) {
|
||||
val statementCommentSaver = CommentSaver(initialization.statement, saveLineBreaks = true)
|
||||
val restoreStatementCommentsTarget: KtExpression
|
||||
when (initialization) {
|
||||
is ConstructorParameterInitialization -> {
|
||||
initialization.mergePropertyAndConstructorParameter()
|
||||
restoreStatementCommentsTarget = initialization.initializer
|
||||
}
|
||||
is LiteralInitialization -> {
|
||||
val (property, initializer, _) = initialization
|
||||
property.initializer = initializer
|
||||
restoreStatementCommentsTarget = property
|
||||
}
|
||||
}
|
||||
initialization.statement.delete()
|
||||
statementCommentSaver.restore(restoreStatementCommentsTarget, forceAdjustIndent = false)
|
||||
}
|
||||
klass.removeEmptyInitBlocks()
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Initialization<I : KtElement> {
|
||||
abstract val property: KtProperty
|
||||
abstract val initializer: I
|
||||
abstract val statement: KtBinaryExpression
|
||||
}
|
||||
|
||||
private data class ConstructorParameterInitialization(
|
||||
override val property: KtProperty,
|
||||
override val initializer: KtParameter,
|
||||
override val statement: KtBinaryExpression
|
||||
) : Initialization<KtParameter>()
|
||||
|
||||
private data class LiteralInitialization(
|
||||
override val property: KtProperty,
|
||||
override val initializer: KtExpression,
|
||||
override val statement: KtBinaryExpression
|
||||
) : Initialization<KtExpression>()
|
||||
|
||||
+7
-12
@@ -1,5 +1,6 @@
|
||||
internal class A @JvmOverloads constructor(p: Int = 1) {
|
||||
private val v: Int
|
||||
internal class A // end of primary constructor body
|
||||
@JvmOverloads constructor(p: Int = 1) {
|
||||
private val v = 1
|
||||
|
||||
// this is a secondary constructor 2
|
||||
constructor(s: String) : this(s.length) {} // end of secondary constructor 2 body
|
||||
@@ -8,9 +9,6 @@ internal class A @JvmOverloads constructor(p: Int = 1) {
|
||||
|
||||
|
||||
// this is a primary constructor
|
||||
init {
|
||||
v = 1
|
||||
} // end of primary constructor body
|
||||
|
||||
|
||||
// end of secondary constructor 1 body
|
||||
@@ -24,18 +22,15 @@ internal class B // end of constructor body
|
||||
}
|
||||
|
||||
internal class CtorComment {
|
||||
var myA: String
|
||||
|
||||
/*
|
||||
var myA = "a"
|
||||
/*
|
||||
* The magic of comments
|
||||
*/
|
||||
// single line magic comments
|
||||
init {
|
||||
myA = "a"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class CtorComment2 /*
|
||||
* The magic of comments
|
||||
*/
|
||||
// single line magic comments
|
||||
// single line magic comments
|
||||
|
||||
+2
-6
@@ -1,6 +1,6 @@
|
||||
internal class C(val myArg1: Int) {
|
||||
var myArg2: Int
|
||||
var myArg3: Int
|
||||
var myArg2 = 0
|
||||
var myArg3 = 0
|
||||
|
||||
constructor(arg1: Int, arg2: Int, arg3: Int) : this(arg1) {
|
||||
myArg2 = arg2
|
||||
@@ -12,10 +12,6 @@ internal class C(val myArg1: Int) {
|
||||
myArg3 = 0
|
||||
}
|
||||
|
||||
init {
|
||||
myArg2 = 0
|
||||
myArg3 = 0
|
||||
}
|
||||
}
|
||||
|
||||
object User {
|
||||
|
||||
+9
-2
@@ -1,12 +1,16 @@
|
||||
package org.test.customer
|
||||
|
||||
internal class Customer(val firstName: String, val lastName: String) {
|
||||
internal class Customer(first: String, last: String) {
|
||||
val firstName: String
|
||||
val lastName: String
|
||||
|
||||
private fun doSmthBefore() {}
|
||||
private fun doSmthAfter() {}
|
||||
|
||||
init {
|
||||
doSmthBefore()
|
||||
firstName = first
|
||||
lastName = last
|
||||
doSmthAfter()
|
||||
}
|
||||
}
|
||||
@@ -31,7 +35,10 @@ internal class CustomerBuilder {
|
||||
|
||||
object User {
|
||||
fun main() {
|
||||
val customer = CustomerBuilder().WithFirstName("Homer").WithLastName("Simpson").Build()
|
||||
val customer = CustomerBuilder()
|
||||
.WithFirstName("Homer")
|
||||
.WithLastName("Simpson")
|
||||
.Build()
|
||||
println(customer.firstName)
|
||||
println(customer.lastName)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
internal class C(p: Int) {
|
||||
var p: Int
|
||||
var p = 0
|
||||
|
||||
init {
|
||||
this.p = 0
|
||||
if (p > 0) {
|
||||
this.p = p
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
class Test(count: Int) {
|
||||
var count: Int
|
||||
private set
|
||||
class Test(var count: Int) {
|
||||
|
||||
fun inc() {
|
||||
this.count++
|
||||
}
|
||||
|
||||
init {
|
||||
this.count = count
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ open class Base internal constructor(x: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
internal class Derived(b: Base) : Base(b.x)
|
||||
internal class Derived(b: Base) : Base(b.x)
|
||||
@@ -1,6 +1,6 @@
|
||||
internal class C(val arg1: Int) {
|
||||
var arg2: Int
|
||||
var arg3: Int
|
||||
var arg2 = 0
|
||||
var arg3 = 0
|
||||
|
||||
constructor(arg1: Int, arg2: Int, arg3: Int) : this(arg1) {
|
||||
this.arg2 = arg2
|
||||
@@ -12,9 +12,4 @@ internal class C(val arg1: Int) {
|
||||
arg3 = 0
|
||||
}
|
||||
|
||||
init {
|
||||
arg2 = 0
|
||||
arg3 = 0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
public class TestInitInCtor {
|
||||
private int i;
|
||||
private int j;
|
||||
|
||||
public TestInitInCtor(int i) {
|
||||
this.i = i;
|
||||
j = i;
|
||||
}
|
||||
|
||||
public int foo() {
|
||||
return i + j;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
class TestInitInCtor(private val i: Int) {
|
||||
private val j: Int
|
||||
fun foo(): Int {
|
||||
return i + j
|
||||
}
|
||||
|
||||
init {
|
||||
j = i
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
internal class CtorComment {
|
||||
var myA: String
|
||||
internal class CtorComment
|
||||
/**
|
||||
* This constructor is especially useful
|
||||
*/
|
||||
{
|
||||
var myA = "str"
|
||||
|
||||
/**
|
||||
* This constructor is especially useful
|
||||
*/
|
||||
init {
|
||||
myA = "str"
|
||||
}
|
||||
}
|
||||
|
||||
internal class CtorComment2
|
||||
/**
|
||||
* This constructor is especially useful
|
||||
*/
|
||||
*/
|
||||
|
||||
+2
-9
@@ -1,22 +1,15 @@
|
||||
class Init {
|
||||
var field1: String
|
||||
var field1 = "str"
|
||||
var field2: String? = null
|
||||
|
||||
var field3: Int
|
||||
var field3 = 1
|
||||
var field4 = 0
|
||||
|
||||
init {
|
||||
field1 = "str"
|
||||
field3 = 1
|
||||
|
||||
val prop1: String
|
||||
prop1 = "aaa"
|
||||
|
||||
var prop2: String
|
||||
|
||||
val prop3: Int
|
||||
prop3 = 1
|
||||
|
||||
var prop4: Int
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -4,8 +4,8 @@ internal class A(private val field6: Int, private val field8: Int, a: A) {
|
||||
private var field3 = 0
|
||||
val field4 = 0
|
||||
var field5 = 0
|
||||
private val field7: Int
|
||||
private val field9: Int
|
||||
private val field7 = 10
|
||||
private val field9 = 10
|
||||
private var field10 = 0
|
||||
private var field11 = 0
|
||||
fun foo() {
|
||||
@@ -13,8 +13,6 @@ internal class A(private val field6: Int, private val field8: Int, a: A) {
|
||||
}
|
||||
|
||||
init {
|
||||
field7 = 10
|
||||
field9 = 10
|
||||
if (field6 > 0) {
|
||||
field10 = 10
|
||||
}
|
||||
|
||||
+5
@@ -1526,6 +1526,11 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/kt-31700.java");
|
||||
}
|
||||
|
||||
@TestMetadata("kt-32253.java")
|
||||
public void testKt_32253() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/kt-32253.java");
|
||||
}
|
||||
|
||||
@TestMetadata("Overrides.java")
|
||||
public void testOverrides() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/Overrides.java");
|
||||
|
||||
Reference in New Issue
Block a user