Skip to content

Commit

Permalink
Rename @ArrayContentBased to @Poko.ReadArrayContent (#449)
Browse files Browse the repository at this point in the history
* Replace ArrayContentBased with Poko.ReadArrayContent

This allows support for using this feature with a custom Poko annotation.

Change ArrayContentBased to a deprecated typealias for compatibility.

* Replace project usages of ArrayContentBased with ReadArrayContent
  • Loading branch information
drewhamilton authored Dec 14, 2024
1 parent 1838bd6 commit 55ce748
Show file tree
Hide file tree
Showing 16 changed files with 108 additions and 96 deletions.
6 changes: 3 additions & 3 deletions poko-annotations/api/poko-annotations.api
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
public abstract interface annotation class dev/drewhamilton/poko/ArrayContentBased : java/lang/annotation/Annotation {
}

public abstract interface annotation class dev/drewhamilton/poko/ArrayContentSupport : java/lang/annotation/Annotation {
}

public abstract interface annotation class dev/drewhamilton/poko/Poko : java/lang/annotation/Annotation {
}

public abstract interface annotation class dev/drewhamilton/poko/Poko$ReadArrayContent : java/lang/annotation/Annotation {
}

public abstract interface annotation class dev/drewhamilton/poko/Poko$Skip : java/lang/annotation/Annotation {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
package dev.drewhamilton.poko

/**
* Declares that a [Poko] class's generated functions will be based on this property's array
* content. This differs from the Poko class (and data class) default of comparing arrays by
* reference only.
*
* Poko class properties of type [Array], [BooleanArray], [CharArray], [ByteArray], [ShortArray],
* [IntArray], [LongArray], [FloatArray], and [DoubleArray] are supported, including nested
* [Array] types.
*
* Properties of a generic type or of type [Any] are also supported. For these properties, Poko will
* generate a `when` statement that disambiguates the various array types at runtime and analyzes
* content if the property is an array. (Note that with this logic, typed arrays will never be
* considered equals to primitive arrays, even if they hold the same content. For example,
* `arrayOf(1, 2)` will not be considered equals to `intArrayOf(1, 2)`.)
*
* Properties of a value class type that wraps an array are not supported. Tagging non-array
* properties with this annotation is an error.
*
* Using array properties in data models is not generally recommended, because they are mutable.
* Mutating an array marked with this annotation will cause the parent Poko class to produce
* different `equals` and `hashCode` results at different times. This annotation should only be used
* by consumers for whom performant code is more important than safe code.
* Legacy name for [Poko.ReadArrayContent].
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.PROPERTY)
public annotation class ArrayContentBased
@Deprecated(
message = "Moved to @Poko.ReadArrayContent for compatibility with custom Poko annotation",
replaceWith = ReplaceWith("Poko.ReadArrayContent"),
)
public typealias ArrayContentBased = Poko.ReadArrayContent
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,31 @@ public annotation class Poko {
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.PROPERTY)
public annotation class Skip

/**
* Declares that a [Poko] class's generated functions will be based on this property's array
* content. This differs from the Poko class (and data class) default of comparing arrays by
* reference only.
*
* Poko class properties of type [Array], [BooleanArray], [CharArray], [ByteArray], [ShortArray],
* [IntArray], [LongArray], [FloatArray], and [DoubleArray] are supported, including nested
* [Array] types.
*
* Properties of a generic type or of type [Any] are also supported. For these properties, Poko will
* generate a `when` statement that disambiguates the various array types at runtime and analyzes
* content if the property is an array. (Note that with this logic, typed arrays will never be
* considered equals to primitive arrays, even if they hold the same content. For example,
* `arrayOf(1, 2)` will not be considered equals to `intArrayOf(1, 2)`.)
*
* Properties of a value class type that wraps an array are not supported. Tagging non-array
* properties with this annotation is an error.
*
* Using array properties in data models is not generally recommended, because they are mutable.
* Mutating an array marked with this annotation will cause the parent Poko class to produce
* different `equals` and `hashCode` results at different times. This annotation should only be used
* by consumers for whom performant code is more important than safe code.
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.PROPERTY)
public annotation class ReadArrayContent
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal class PokoMembersTransformer(
when {
declaration.isEquals() -> declaration.convertToGenerated { properties ->
generateEqualsMethodBody(
pokoAnnotation = pokoAnnotationName,
context = pluginContext,
irClass = declarationParent,
functionDeclaration = declaration,
Expand All @@ -55,6 +56,7 @@ internal class PokoMembersTransformer(

declaration.isHashCode() -> declaration.convertToGenerated { properties ->
generateHashCodeMethodBody(
pokoAnnotation = pokoAnnotationName,
context = pluginContext,
functionDeclaration = declaration,
classProperties = properties,
Expand All @@ -64,6 +66,7 @@ internal class PokoMembersTransformer(

declaration.isToString() -> declaration.convertToGenerated { properties ->
generateToStringMethodBody(
pokoAnnotation = pokoAnnotationName,
context = pluginContext,
irClass = declarationParent,
functionDeclaration = declaration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isArrayOrPrimitiveArray
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name

Expand All @@ -48,6 +49,7 @@ import org.jetbrains.kotlin.name.Name
* [org.jetbrains.kotlin.ir.util.DataClassMembersGenerator.MemberFunctionBuilder.generateEqualsMethodBody].
*/
internal fun IrBlockBodyBuilder.generateEqualsMethodBody(
pokoAnnotation: ClassId,
context: IrPluginContext,
irClass: IrClass,
functionDeclaration: IrFunction,
Expand All @@ -66,7 +68,7 @@ internal fun IrBlockBodyBuilder.generateEqualsMethodBody(
val arg1 = irGetField(receiver(functionDeclaration), field)
val arg2 = irGetField(irGet(irType, otherWithCast.symbol), field)
val irNotEquals = when {
property.hasArrayContentBasedAnnotation() -> {
property.hasReadArrayContentAnnotation(pokoAnnotation) -> {
irNot(
irArrayContentDeepEquals(
context = context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name

/**
Expand Down Expand Up @@ -60,13 +59,11 @@ internal fun IrBlockBodyBuilder.IrGetValueImpl(
)
}

internal fun IrProperty.hasArrayContentBasedAnnotation(): Boolean =
hasAnnotation(arrayContentBasedAnnotationFqName)

private val arrayContentBasedAnnotationFqName = ClassId(
FqName("dev.drewhamilton.poko"),
Name.identifier("ArrayContentBased"),
).asSingleFqName()
internal fun IrProperty.hasReadArrayContentAnnotation(
pokoAnnotation: ClassId,
): Boolean = hasAnnotation(
classId = pokoAnnotation.createNestedClassId(Name.identifier("ReadArrayContent")),
)

/**
* Returns true if the classifier represents a type that may be an array at runtime (e.g. [Any] or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.isArrayOrPrimitiveArray
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name

Expand All @@ -48,6 +49,7 @@ import org.jetbrains.kotlin.name.Name
* [org.jetbrains.kotlin.ir.util.DataClassMembersGenerator.MemberFunctionBuilder.generateHashCodeMethodBody].
*/
internal fun IrBlockBodyBuilder.generateHashCodeMethodBody(
pokoAnnotation: ClassId,
context: IrPluginContext,
functionDeclaration: IrFunction,
classProperties: List<IrProperty>,
Expand All @@ -59,6 +61,7 @@ internal fun IrBlockBodyBuilder.generateHashCodeMethodBody(
} else if (classProperties.size == 1) {
+irReturn(
getHashCodeOfProperty(
pokoAnnotation = pokoAnnotation,
context = context,
function = functionDeclaration,
property = classProperties[0],
Expand All @@ -83,6 +86,7 @@ internal fun IrBlockBodyBuilder.generateHashCodeMethodBody(
).also {
it.parent = functionDeclaration
it.initializer = getHashCodeOfProperty(
pokoAnnotation = pokoAnnotation,
context = context,
function = functionDeclaration,
property = classProperties[0],
Expand All @@ -103,6 +107,7 @@ internal fun IrBlockBodyBuilder.generateHashCodeMethodBody(
type = irIntType,
dispatchReceiver = shiftedResult,
argument = getHashCodeOfProperty(
pokoAnnotation = pokoAnnotation,
context = context,
function = functionDeclaration,
property = property,
Expand All @@ -119,6 +124,7 @@ internal fun IrBlockBodyBuilder.generateHashCodeMethodBody(
* Generates the hashcode-computing code for [property].
*/
private fun IrBlockBodyBuilder.getHashCodeOfProperty(
pokoAnnotation: ClassId,
context: IrPluginContext,
function: IrFunction,
property: IrProperty,
Expand All @@ -131,9 +137,9 @@ private fun IrBlockBodyBuilder.getHashCodeOfProperty(
type = context.irBuiltIns.intType,
subject = irGetField(),
thenPart = irInt(0),
elsePart = getHashCodeOf(context, property, irGetField(), messageCollector)
elsePart = getHashCodeOf(pokoAnnotation, context, property, irGetField(), messageCollector)
)
else -> getHashCodeOf(context, property, irGetField(), messageCollector)
else -> getHashCodeOf(pokoAnnotation, context, property, irGetField(), messageCollector)
}
}

Expand All @@ -143,6 +149,7 @@ private fun IrBlockBodyBuilder.getHashCodeOfProperty(
*/
@OptIn(UnsafeDuringIrConstructionAPI::class)
private fun IrBlockBodyBuilder.getHashCodeOf(
pokoAnnotation: ClassId,
context: IrPluginContext,
property: IrProperty,
value: IrExpression,
Expand All @@ -161,7 +168,7 @@ private fun IrBlockBodyBuilder.getHashCodeOf(
}
}

val hasArrayContentBasedAnnotation = property.hasArrayContentBasedAnnotation()
val hasArrayContentBasedAnnotation = property.hasReadArrayContentAnnotation(pokoAnnotation)
val classifier = property.type.classifierOrNull

if (hasArrayContentBasedAnnotation && classifier.mayBeRuntimeArray(context)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.util.isArrayOrPrimitiveArray
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name

Expand All @@ -38,6 +39,7 @@ import org.jetbrains.kotlin.name.Name
* [org.jetbrains.kotlin.ir.util.DataClassMembersGenerator.MemberFunctionBuilder.generateToStringMethodBody].
*/
internal fun IrBlockBodyBuilder.generateToStringMethodBody(
pokoAnnotation: ClassId,
context: IrPluginContext,
irClass: IrClass,
functionDeclaration: IrFunction,
Expand All @@ -56,7 +58,7 @@ internal fun IrBlockBodyBuilder.generateToStringMethodBody(
val propertyValue = irGetField(receiver(functionDeclaration), property.backingField!!)

val classifier = property.type.classifierOrNull
val hasArrayContentBasedAnnotation = property.hasArrayContentBasedAnnotation()
val hasArrayContentBasedAnnotation = property.hasReadArrayContentAnnotation(pokoAnnotation)
val propertyStringValue = when {
hasArrayContentBasedAnnotation && classifier.mayBeRuntimeArray(context) -> {
val field = property.backingField!!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package poko

import dev.drewhamilton.poko.ArrayContentBased
import dev.drewhamilton.poko.Poko

@Suppress("Unused")
@Poko class AnyArrayHolder(
@ArrayContentBased val any: Any,
@ArrayContentBased val nullableAny: Any?,
@Poko.ReadArrayContent val any: Any,
@Poko.ReadArrayContent val nullableAny: Any?,
val trailingProperty: String,
)
41 changes: 20 additions & 21 deletions poko-tests-without-k2/src/commonMain/kotlin/poko/ArrayHolder.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
package poko

import dev.drewhamilton.poko.ArrayContentBased
import dev.drewhamilton.poko.Poko

@Suppress("Unused")
@Poko class ArrayHolder(
@ArrayContentBased val stringArray: Array<String>,
@ArrayContentBased val nullableStringArray: Array<String>?,
@ArrayContentBased val booleanArray: BooleanArray,
@ArrayContentBased val nullableBooleanArray: BooleanArray?,
@ArrayContentBased val byteArray: ByteArray,
@ArrayContentBased val nullableByteArray: ByteArray?,
@ArrayContentBased val charArray: CharArray,
@ArrayContentBased val nullableCharArray: CharArray?,
@ArrayContentBased val shortArray: ShortArray,
@ArrayContentBased val nullableShortArray: ShortArray?,
@ArrayContentBased val intArray: IntArray,
@ArrayContentBased val nullableIntArray: IntArray?,
@ArrayContentBased val longArray: LongArray,
@ArrayContentBased val nullableLongArray: LongArray?,
@ArrayContentBased val floatArray: FloatArray,
@ArrayContentBased val nullableFloatArray: FloatArray?,
@ArrayContentBased val doubleArray: DoubleArray,
@ArrayContentBased val nullableDoubleArray: DoubleArray?,
@ArrayContentBased val nestedStringArray: Array<Array<String>>,
@ArrayContentBased val nestedIntArray: Array<IntArray>,
@Poko.ReadArrayContent val stringArray: Array<String>,
@Poko.ReadArrayContent val nullableStringArray: Array<String>?,
@Poko.ReadArrayContent val booleanArray: BooleanArray,
@Poko.ReadArrayContent val nullableBooleanArray: BooleanArray?,
@Poko.ReadArrayContent val byteArray: ByteArray,
@Poko.ReadArrayContent val nullableByteArray: ByteArray?,
@Poko.ReadArrayContent val charArray: CharArray,
@Poko.ReadArrayContent val nullableCharArray: CharArray?,
@Poko.ReadArrayContent val shortArray: ShortArray,
@Poko.ReadArrayContent val nullableShortArray: ShortArray?,
@Poko.ReadArrayContent val intArray: IntArray,
@Poko.ReadArrayContent val nullableIntArray: IntArray?,
@Poko.ReadArrayContent val longArray: LongArray,
@Poko.ReadArrayContent val nullableLongArray: LongArray?,
@Poko.ReadArrayContent val floatArray: FloatArray,
@Poko.ReadArrayContent val nullableFloatArray: FloatArray?,
@Poko.ReadArrayContent val doubleArray: DoubleArray,
@Poko.ReadArrayContent val nullableDoubleArray: DoubleArray?,
@Poko.ReadArrayContent val nestedStringArray: Array<Array<String>>,
@Poko.ReadArrayContent val nestedIntArray: Array<IntArray>,
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package poko

import dev.drewhamilton.poko.ArrayContentBased
import dev.drewhamilton.poko.Poko

@Suppress("Unused")
@Poko class ComplexGenericArrayHolder<A : Any, G : A>(
@ArrayContentBased val generic: G,
@Poko.ReadArrayContent val generic: G,
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package poko

import dev.drewhamilton.poko.ArrayContentBased
import dev.drewhamilton.poko.Poko

@Suppress("Unused")
@Poko class GenericArrayHolder<G>(
@ArrayContentBased val generic: G,
@Poko.ReadArrayContent val generic: G,
)
5 changes: 2 additions & 3 deletions poko-tests/src/commonMain/kotlin/poko/AnyArrayHolder.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package poko

import dev.drewhamilton.poko.ArrayContentBased
import dev.drewhamilton.poko.Poko

@Suppress("Unused")
@Poko class AnyArrayHolder(
@ArrayContentBased val any: Any,
@ArrayContentBased val nullableAny: Any?,
@Poko.ReadArrayContent val any: Any,
@Poko.ReadArrayContent val nullableAny: Any?,
val trailingProperty: String,
)
Loading

0 comments on commit 55ce748

Please sign in to comment.