Skip to content

Commit

Permalink
Replace ArrayContentBased with Poko.ReadArrayContent
Browse files Browse the repository at this point in the history
This allows support for using this feature with a custom Poko annotation.

Change ArrayContentBased to a deprecated typealias for compatibility.
  • Loading branch information
drewhamilton committed Dec 13, 2024
1 parent 1838bd6 commit d8c58b5
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 40 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

0 comments on commit d8c58b5

Please sign in to comment.