codegen-kotlin: Fix generation of copy() methods (#705)

- don't generate copy methods for abstract classes
- precisely determine if copy method needs override modifier (tricky)

Fixes #569
This commit is contained in:
translatenix
2024-10-19 20:32:21 -07:00
committed by GitHub
parent d271b62543
commit 84f4ec863c
2 changed files with 217 additions and 1 deletions

View File

@@ -274,9 +274,20 @@ class KotlinCodeGenerator(
return methodBuilder.addCode(codeBuilder.build()).build()
}
fun inheritsCopyMethodWithSameArity(): Boolean {
val nearestNonAbstractAncestor =
generateSequence(pClass.superclass) { it.superclass }.firstOrNull { !it.isAbstract }
?: return false
return nearestNonAbstractAncestor.allProperties.values.count { !it.isHidden } ==
allProperties.size
}
// besides generating copy method for current class,
// override copy methods inherited from parent classes
fun generateCopyMethods(typeBuilder: TypeSpec.Builder) {
// copy methods don't make sense for abstract classes
if (pClass.isAbstract) return
var prevParameterCount = Int.MAX_VALUE
for (currClass in generateSequence(pClass) { it.superclass }) {
if (currClass.isAbstract) continue
@@ -285,7 +296,7 @@ class KotlinCodeGenerator(
// avoid generating multiple methods with same no. of parameters
if (currParameters.size < prevParameterCount) {
val isOverride = currClass !== pClass || superclass != null && properties.isEmpty()
val isOverride = currClass !== pClass || inheritsCopyMethodWithSameArity()
typeBuilder.addFunction(generateCopyMethod(currParameters, isOverride))
prevParameterCount = currParameters.size
}

View File

@@ -661,6 +661,211 @@ class KotlinCodeGeneratorTest {
assertCompilesSuccessfully(kotlinCode)
}
// https://github.com/apple/pkl/issues/569
@Test
fun `abstract classes`() {
val kotlinCode =
generateKotlinCode(
"""
module my.mod
abstract class Foo { one: Int }
abstract class Bar extends Foo { two: String }
class Baz extends Bar { three: Duration }
class Qux extends Bar {}
"""
)
assertContains(
"""
| abstract class Foo(
| open val one: Long
| ) {
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| abstract class Bar(
| one: Long,
| open val two: String
| ) : Foo(one) {
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| class Baz(
| one: Long,
| two: String,
| val three: Duration
| ) : Bar(one, two) {
| fun copy(
| one: Long = this.one,
| two: String = this.two,
| three: Duration = this.three
| ): Baz = Baz(one, two, three)
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| class Qux(
| one: Long,
| two: String
| ) : Bar(one, two) {
| fun copy(one: Long = this.one, two: String = this.two): Qux = Qux(one, two)
"""
.trimMargin(),
kotlinCode
)
assertCompilesSuccessfully(kotlinCode)
}
// https://github.com/apple/pkl/issues/569
@Test
fun `abstract class that extends open class`() {
val kotlinCode =
generateKotlinCode(
"""
module my.mod
open class Foo { one: Int }
abstract class Bar extends Foo { two: String }
class Baz extends Bar { three: Duration }
class Qux extends Bar {}
"""
)
assertContains(
"""
| open class Foo(
| open val one: Long
| ) {
| open fun copy(one: Long = this.one): Foo = Foo(one)
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| abstract class Bar(
| one: Long,
| open val two: String
| ) : Foo(one) {
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| class Baz(
| one: Long,
| two: String,
| val three: Duration
| ) : Bar(one, two) {
| fun copy(
| one: Long = this.one,
| two: String = this.two,
| three: Duration = this.three
| ): Baz = Baz(one, two, three)
|
| override fun copy(one: Long): Baz = Baz(one, two, three)
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| class Qux(
| one: Long,
| two: String
| ) : Bar(one, two) {
| fun copy(one: Long = this.one, two: String = this.two): Qux = Qux(one, two)
|
| override fun copy(one: Long): Qux = Qux(one, two)
"""
.trimMargin(),
kotlinCode
)
assertCompilesSuccessfully(kotlinCode)
}
// https://github.com/apple/pkl/issues/569
@Test
fun `abstract class that extends open class without adding properties`() {
val kotlinCode =
generateKotlinCode(
"""
module my.mod
open class Foo { one: Int }
abstract class Bar extends Foo {}
class Baz extends Bar { two: Duration }
class Qux extends Bar {}
"""
)
assertContains(
"""
| open class Foo(
| open val one: Long
| ) {
| open fun copy(one: Long = this.one): Foo = Foo(one)
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| abstract class Bar(
| one: Long
| ) : Foo(one) {
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| class Baz(
| one: Long,
| val two: Duration
| ) : Bar(one) {
| fun copy(one: Long = this.one, two: Duration = this.two): Baz = Baz(one, two)
|
| override fun copy(one: Long): Baz = Baz(one, two)
"""
.trimMargin(),
kotlinCode
)
assertContains(
"""
| class Qux(
| one: Long
| ) : Bar(one) {
| override fun copy(one: Long): Qux = Qux(one)
"""
.trimMargin(),
kotlinCode
)
assertCompilesSuccessfully(kotlinCode)
}
@Test
fun keywords() {
val props = kotlinKeywords.joinToString("\n") { "`$it`: Int" }