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