mirror of
https://github.com/apple/pkl.git
synced 2026-04-22 08:18:32 +02:00
Introduces Bytes class (#1019)
This introduces a new `Bytes` standard library class, for working with binary data. * Add Bytes class to the standard library * Change CLI to eval `output.bytes` * Change code generators to map Bytes to respective underlying type * Add subscript and concat operator support * Add binary encoding for Bytes * Add PCF and Plist rendering for Bytes Co-authored-by: Kushal Pisavadia <kushi.p@gmail.com>
This commit is contained in:
@@ -226,6 +226,9 @@ class KotlinCodeGenerator(
|
||||
fun PClass.Property.isRegex(): Boolean =
|
||||
(this.type as? PType.Class)?.pClass?.info == PClassInfo.Regex
|
||||
|
||||
fun PClass.Property.isByteArray(): Boolean =
|
||||
(this.type as? PType.Class)?.pClass?.info == PClassInfo.Bytes
|
||||
|
||||
fun generateConstructor(): FunSpec {
|
||||
val builder = FunSpec.constructorBuilder()
|
||||
for ((name, property) in allProperties) {
|
||||
@@ -302,12 +305,20 @@ class KotlinCodeGenerator(
|
||||
.addStatement("other as %T", kotlinPoetClassName)
|
||||
|
||||
for ((propertyName, property) in allProperties) {
|
||||
val accessor = if (property.isRegex()) "%N.pattern" else "%N"
|
||||
builder.addStatement(
|
||||
"if (this.$accessor != other.$accessor) return false",
|
||||
propertyName,
|
||||
propertyName,
|
||||
)
|
||||
if (property.isByteArray()) {
|
||||
builder.addStatement(
|
||||
"if (!this.%N.contentEquals(other.%N)) return false",
|
||||
propertyName,
|
||||
propertyName,
|
||||
)
|
||||
} else {
|
||||
val accessor = if (property.isRegex()) "%N.pattern" else "%N"
|
||||
builder.addStatement(
|
||||
"if (this.$accessor != other.$accessor) return false",
|
||||
propertyName,
|
||||
propertyName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
builder.addStatement("return true")
|
||||
@@ -322,14 +333,18 @@ class KotlinCodeGenerator(
|
||||
.addStatement("var result = 1")
|
||||
|
||||
for ((propertyName, property) in allProperties) {
|
||||
val accessor = if (property.isRegex()) "this.%N.pattern" else "this.%N"
|
||||
// use Objects.hashCode() because Kotlin's Any?.hashCode()
|
||||
// doesn't work for platform types (will get NPE if null)
|
||||
builder.addStatement(
|
||||
"result = 31 * result + %T.hashCode($accessor)",
|
||||
Objects::class,
|
||||
propertyName,
|
||||
)
|
||||
if (property.isByteArray()) {
|
||||
builder.addStatement("result = 31 * result + this.%N.contentHashCode()", propertyName)
|
||||
} else {
|
||||
val accessor = if (property.isRegex()) "this.%N.pattern" else "this.%N"
|
||||
// use Objects.hashCode() because Kotlin's Any?.hashCode()
|
||||
// doesn't work for platform types (will get NPE if null)
|
||||
builder.addStatement(
|
||||
"result = 31 * result + %T.hashCode($accessor)",
|
||||
Objects::class,
|
||||
propertyName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
builder.addStatement("return result")
|
||||
@@ -347,10 +362,15 @@ class KotlinCodeGenerator(
|
||||
.apply {
|
||||
add("%L", pClass.toKotlinPoetName().simpleName)
|
||||
add("(")
|
||||
for ((index, propertyName) in allProperties.keys.withIndex()) {
|
||||
for ((index, entry) in allProperties.entries.withIndex()) {
|
||||
val (propertyName, property) = entry
|
||||
add(if (index == 0) "%L" else ", %L", propertyName)
|
||||
add("=$")
|
||||
add("%N", propertyName)
|
||||
if (property.isByteArray()) {
|
||||
add("=\${%T.toString(%L)}", Arrays::class, propertyName)
|
||||
} else {
|
||||
add("=$")
|
||||
add("%N", propertyName)
|
||||
}
|
||||
}
|
||||
add(")")
|
||||
}
|
||||
@@ -488,16 +508,16 @@ class KotlinCodeGenerator(
|
||||
builder.addKdoc(renderAsKdoc(docComment))
|
||||
}
|
||||
|
||||
var hasRegex = false
|
||||
var hasRegexOrByteArray = false
|
||||
for ((name, property) in properties) {
|
||||
hasRegex = hasRegex || property.isRegex()
|
||||
hasRegexOrByteArray = hasRegexOrByteArray || property.isRegex() || property.isByteArray()
|
||||
builder.addProperty(generateProperty(name, property))
|
||||
}
|
||||
|
||||
// kotlin.text.Regex (and java.util.regex.Pattern) defines equality as identity.
|
||||
// To match Pkl semantics and compare regexes by their String pattern,
|
||||
// override equals and hashCode if the data class has a property of type Regex.
|
||||
if (hasRegex) {
|
||||
// Regex and ByteArray define equaltiy as identity.
|
||||
// To match Pkl semantics, override equals and hashCode if the data class has a property of
|
||||
// type Regex or ByteArray.
|
||||
if (hasRegexOrByteArray) {
|
||||
builder.addFunction(generateEqualsMethod()).addFunction(generateHashCodeMethod())
|
||||
}
|
||||
|
||||
@@ -632,6 +652,7 @@ class KotlinCodeGenerator(
|
||||
PClassInfo.Float -> DOUBLE
|
||||
PClassInfo.Duration -> DURATION
|
||||
PClassInfo.DataSize -> DATA_SIZE
|
||||
PClassInfo.Bytes -> BYTE_ARRAY
|
||||
PClassInfo.Pair ->
|
||||
KOTLIN_PAIR.parameterizedBy(
|
||||
if (typeArguments.isEmpty()) ANY_NULL else typeArguments[0].toKotlinPoetName(),
|
||||
|
||||
@@ -120,6 +120,7 @@ class KotlinCodeGeneratorTest {
|
||||
any: Any
|
||||
nonNull: NonNull
|
||||
enum: Direction
|
||||
bytes: Bytes
|
||||
}
|
||||
|
||||
open class Other {
|
||||
@@ -193,7 +194,6 @@ class KotlinCodeGeneratorTest {
|
||||
@Test
|
||||
fun testToString() {
|
||||
val (_, propertyTypes) = instantiateOtherAndPropertyTypes()
|
||||
|
||||
assertThat(propertyTypes.toString())
|
||||
.isEqualTo(
|
||||
"""PropertyTypes(boolean=true, int=42, float=42.3, string=string, duration=5.min, """ +
|
||||
@@ -204,7 +204,8 @@ class KotlinCodeGeneratorTest {
|
||||
"""set2=[Other(name=pigeon)], map={1=one, 2=two}, map2={one=Other(name=pigeon), """ +
|
||||
"""two=Other(name=pigeon)}, container={1=one, 2=two}, container2={one=Other(name=pigeon), """ +
|
||||
"""two=Other(name=pigeon)}, other=Other(name=pigeon), regex=(i?)\w*, any=Other(name=pigeon), """ +
|
||||
"""nonNull=Other(name=pigeon), enum=north)"""
|
||||
"""nonNull=Other(name=pigeon), enum=north, """ +
|
||||
"""bytes=[1, 2, 3, 4])"""
|
||||
)
|
||||
}
|
||||
|
||||
@@ -412,6 +413,7 @@ class KotlinCodeGeneratorTest {
|
||||
assertThat(readProperty(propertyTypes, "regex")).isInstanceOf(Regex::class.java)
|
||||
assertThat(readProperty(propertyTypes, "any")).isEqualTo(other)
|
||||
assertThat(readProperty(propertyTypes, "nonNull")).isEqualTo(other)
|
||||
assertThat(readProperty(propertyTypes, "bytes")).isEqualTo(byteArrayOf(1, 2, 3, 4))
|
||||
}
|
||||
|
||||
private fun readProperty(receiver: Any, name: String): Any? {
|
||||
@@ -571,6 +573,25 @@ class KotlinCodeGeneratorTest {
|
||||
.contains("result = 31 * result + Objects.hashCode(this.name.pattern)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data class with ByteArray property has custom equals and hashCode methods`() {
|
||||
val kotlinCode =
|
||||
generateKotlinCode(
|
||||
"""
|
||||
module my.mod
|
||||
|
||||
class Foo {
|
||||
bytes: Bytes
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
assertThat(kotlinCode)
|
||||
.contains("if (!this.bytes.contentEquals(other.bytes)) return false")
|
||||
.contains("result = 31 * result + this.bytes.contentHashCode()")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `recursive types`() {
|
||||
val kotlinCode =
|
||||
@@ -2078,6 +2099,7 @@ class KotlinCodeGeneratorTest {
|
||||
other,
|
||||
other,
|
||||
enumValue,
|
||||
byteArrayOf(1, 2, 3, 4),
|
||||
)
|
||||
|
||||
return other to propertyTypes
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package my
|
||||
|
||||
import java.util.Arrays
|
||||
import java.util.Objects
|
||||
import kotlin.Any
|
||||
import kotlin.Boolean
|
||||
import kotlin.ByteArray
|
||||
import kotlin.Double
|
||||
import kotlin.Int
|
||||
import kotlin.Long
|
||||
@@ -46,7 +48,8 @@ object Mod {
|
||||
open val regex: Regex,
|
||||
open val any: Any?,
|
||||
open val nonNull: Any,
|
||||
open val enum: Direction
|
||||
open val enum: Direction,
|
||||
open val bytes: ByteArray
|
||||
) {
|
||||
open fun copy(
|
||||
boolean: Boolean = this.boolean,
|
||||
@@ -75,10 +78,11 @@ object Mod {
|
||||
regex: Regex = this.regex,
|
||||
any: Any? = this.any,
|
||||
nonNull: Any = this.nonNull,
|
||||
enum: Direction = this.enum
|
||||
enum: Direction = this.enum,
|
||||
bytes: ByteArray = this.bytes
|
||||
): PropertyTypes = PropertyTypes(boolean, int, float, string, duration, durationUnit, dataSize,
|
||||
dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map,
|
||||
map2, container, container2, other, regex, any, nonNull, enum)
|
||||
map2, container, container2, other, regex, any, nonNull, enum, bytes)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
@@ -111,6 +115,7 @@ object Mod {
|
||||
if (this.any != other.any) return false
|
||||
if (this.nonNull != other.nonNull) return false
|
||||
if (this.enum != other.enum) return false
|
||||
if (!this.bytes.contentEquals(other.bytes)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -143,11 +148,12 @@ object Mod {
|
||||
result = 31 * result + Objects.hashCode(this.any)
|
||||
result = 31 * result + Objects.hashCode(this.nonNull)
|
||||
result = 31 * result + Objects.hashCode(this.enum)
|
||||
result = 31 * result + this.bytes.contentHashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
"""PropertyTypes(boolean=$boolean, int=$int, float=$float, string=$string, duration=$duration, durationUnit=$durationUnit, dataSize=$dataSize, dataSizeUnit=$dataSizeUnit, nullable=$nullable, nullable2=$nullable2, pair=$pair, pair2=$pair2, coll=$coll, coll2=$coll2, list=$list, list2=$list2, set=$set, set2=$set2, map=$map, map2=$map2, container=$container, container2=$container2, other=$other, regex=$regex, any=$any, nonNull=$nonNull, enum=$enum)"""
|
||||
"""PropertyTypes(boolean=$boolean, int=$int, float=$float, string=$string, duration=$duration, durationUnit=$durationUnit, dataSize=$dataSize, dataSizeUnit=$dataSizeUnit, nullable=$nullable, nullable2=$nullable2, pair=$pair, pair2=$pair2, coll=$coll, coll2=$coll2, list=$list, list2=$list2, set=$set, set2=$set2, map=$map, map2=$map2, container=$container, container2=$container2, other=$other, regex=$regex, any=$any, nonNull=$nonNull, enum=$enum, bytes=${Arrays.toString(bytes)})"""
|
||||
}
|
||||
|
||||
open class Other(
|
||||
|
||||
Reference in New Issue
Block a user