mirror of
https://github.com/apple/pkl.git
synced 2026-04-25 09:48:41 +02:00
codegen-java/kotlin: Fix generation of hashCode methods (#802)
codegen-java: - use `pattern.pattern()` instead of `pattern` in hashCode method (consistent with equals method) codegen-kotlin: - use `regex.pattern` instead of `regex` in hashCode method (consistent with equals method) - if a data class has a Regex property, generate not only an equals method but also a hashCode method
This commit is contained in:
@@ -221,6 +221,9 @@ class JavaCodeGenerator(
|
|||||||
val properties = renameIfReservedWord(pClass.properties).filterValues { !it.isHidden }
|
val properties = renameIfReservedWord(pClass.properties).filterValues { !it.isHidden }
|
||||||
val allProperties = superProperties + properties
|
val allProperties = superProperties + properties
|
||||||
|
|
||||||
|
fun PClass.Property.isRegex(): Boolean =
|
||||||
|
(this.type as? PType.Class)?.pClass?.info == PClassInfo.Regex
|
||||||
|
|
||||||
fun addCtorParameter(
|
fun addCtorParameter(
|
||||||
builder: MethodSpec.Builder,
|
builder: MethodSpec.Builder,
|
||||||
propJavaName: String,
|
propJavaName: String,
|
||||||
@@ -283,9 +286,7 @@ class JavaCodeGenerator(
|
|||||||
.addStatement("\$T other = (\$T) obj", javaPoetClassName, javaPoetClassName)
|
.addStatement("\$T other = (\$T) obj", javaPoetClassName, javaPoetClassName)
|
||||||
|
|
||||||
for ((propertyName, property) in allProperties) {
|
for ((propertyName, property) in allProperties) {
|
||||||
val accessor =
|
val accessor = if (property.isRegex()) "\$N.pattern()" else "\$N"
|
||||||
if ((property.type as? PType.Class)?.pClass?.info == PClassInfo.Regex) "\$N.pattern()"
|
|
||||||
else "\$N"
|
|
||||||
builder.addStatement(
|
builder.addStatement(
|
||||||
"if (!\$T.equals(this.$accessor, other.$accessor)) return false",
|
"if (!\$T.equals(this.$accessor, other.$accessor)) return false",
|
||||||
Objects::class.java,
|
Objects::class.java,
|
||||||
@@ -306,9 +307,10 @@ class JavaCodeGenerator(
|
|||||||
.returns(Int::class.java)
|
.returns(Int::class.java)
|
||||||
.addStatement("int result = 1")
|
.addStatement("int result = 1")
|
||||||
|
|
||||||
for (propertyName in allProperties.keys) {
|
for ((propertyName, property) in allProperties) {
|
||||||
|
val accessor = if (property.isRegex()) "this.\$N.pattern()" else "this.\$N"
|
||||||
builder.addStatement(
|
builder.addStatement(
|
||||||
"result = 31 * result + \$T.hashCode(this.\$N)",
|
"result = 31 * result + \$T.hashCode($accessor)",
|
||||||
Objects::class.java,
|
Objects::class.java,
|
||||||
propertyName
|
propertyName
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ public final class Mod {
|
|||||||
result = 31 * result + Objects.hashCode(this.container);
|
result = 31 * result + Objects.hashCode(this.container);
|
||||||
result = 31 * result + Objects.hashCode(this.container2);
|
result = 31 * result + Objects.hashCode(this.container2);
|
||||||
result = 31 * result + Objects.hashCode(this.other);
|
result = 31 * result + Objects.hashCode(this.other);
|
||||||
result = 31 * result + Objects.hashCode(this.regex);
|
result = 31 * result + Objects.hashCode(this.regex.pattern());
|
||||||
result = 31 * result + Objects.hashCode(this.any);
|
result = 31 * result + Objects.hashCode(this.any);
|
||||||
result = 31 * result + Objects.hashCode(this.nonNull);
|
result = 31 * result + Objects.hashCode(this.nonNull);
|
||||||
result = 31 * result + Objects.hashCode(this._enum);
|
result = 31 * result + Objects.hashCode(this._enum);
|
||||||
|
|||||||
@@ -227,8 +227,6 @@ class KotlinCodeGenerator(
|
|||||||
fun PClass.Property.isRegex(): Boolean =
|
fun PClass.Property.isRegex(): Boolean =
|
||||||
(this.type as? PType.Class)?.pClass?.info == PClassInfo.Regex
|
(this.type as? PType.Class)?.pClass?.info == PClassInfo.Regex
|
||||||
|
|
||||||
val containRegexProperty = properties.values.any { it.isRegex() }
|
|
||||||
|
|
||||||
fun generateConstructor(): FunSpec {
|
fun generateConstructor(): FunSpec {
|
||||||
val builder = FunSpec.constructorBuilder()
|
val builder = FunSpec.constructorBuilder()
|
||||||
for ((name, property) in allProperties) {
|
for ((name, property) in allProperties) {
|
||||||
@@ -324,11 +322,12 @@ class KotlinCodeGenerator(
|
|||||||
.returns(INT)
|
.returns(INT)
|
||||||
.addStatement("var result = 1")
|
.addStatement("var result = 1")
|
||||||
|
|
||||||
for (propertyName in allProperties.keys) {
|
for ((propertyName, property) in allProperties) {
|
||||||
|
val accessor = if (property.isRegex()) "this.%N.pattern" else "this.%N"
|
||||||
// use Objects.hashCode() because Kotlin's Any?.hashCode()
|
// use Objects.hashCode() because Kotlin's Any?.hashCode()
|
||||||
// doesn't work for platform types (will get NPE if null)
|
// doesn't work for platform types (will get NPE if null)
|
||||||
builder.addStatement(
|
builder.addStatement(
|
||||||
"result = 31 * result + %T.hashCode(this.%N)",
|
"result = 31 * result + %T.hashCode($accessor)",
|
||||||
Objects::class,
|
Objects::class,
|
||||||
propertyName
|
propertyName
|
||||||
)
|
)
|
||||||
@@ -490,15 +489,17 @@ class KotlinCodeGenerator(
|
|||||||
builder.addKdoc(renderAsKdoc(docComment))
|
builder.addKdoc(renderAsKdoc(docComment))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasRegex = false
|
||||||
for ((name, property) in properties) {
|
for ((name, property) in properties) {
|
||||||
|
hasRegex = hasRegex || property.isRegex()
|
||||||
builder.addProperty(generateProperty(name, property))
|
builder.addProperty(generateProperty(name, property))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regex requires special approach when compared to another Regex
|
// kotlin.text.Regex (and java.util.regex.Pattern) defines equality as identity.
|
||||||
// So we need to override `.equals` method even for kotlin's `data class`es if
|
// To match Pkl semantics and compare regexes by their String pattern,
|
||||||
// any of the properties is of Regex type
|
// override equals and hashCode if the data class has a property of type Regex.
|
||||||
if (containRegexProperty) {
|
if (hasRegex) {
|
||||||
builder.addFunction(generateEqualsMethod())
|
builder.addFunction(generateEqualsMethod()).addFunction(generateHashCodeMethod())
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
|
|||||||
@@ -561,6 +561,26 @@ class KotlinCodeGeneratorTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `data class with Regex property has custom equals and hashCode methods`() {
|
||||||
|
val kotlinCode =
|
||||||
|
generateKotlinCode(
|
||||||
|
"""
|
||||||
|
module my.mod
|
||||||
|
|
||||||
|
class Person {
|
||||||
|
age: Int
|
||||||
|
name: Regex
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(kotlinCode)
|
||||||
|
.contains("if (this.name.pattern != other.name.pattern) return false")
|
||||||
|
.contains("result = 31 * result + Objects.hashCode(this.name.pattern)")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `recursive types`() {
|
fun `recursive types`() {
|
||||||
val kotlinCode =
|
val kotlinCode =
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ object Mod {
|
|||||||
result = 31 * result + Objects.hashCode(this.container)
|
result = 31 * result + Objects.hashCode(this.container)
|
||||||
result = 31 * result + Objects.hashCode(this.container2)
|
result = 31 * result + Objects.hashCode(this.container2)
|
||||||
result = 31 * result + Objects.hashCode(this.other)
|
result = 31 * result + Objects.hashCode(this.other)
|
||||||
result = 31 * result + Objects.hashCode(this.regex)
|
result = 31 * result + Objects.hashCode(this.regex.pattern)
|
||||||
result = 31 * result + Objects.hashCode(this.any)
|
result = 31 * result + Objects.hashCode(this.any)
|
||||||
result = 31 * result + Objects.hashCode(this.nonNull)
|
result = 31 * result + Objects.hashCode(this.nonNull)
|
||||||
result = 31 * result + Objects.hashCode(this.enum)
|
result = 31 * result + Objects.hashCode(this.enum)
|
||||||
|
|||||||
Reference in New Issue
Block a user