From d8c58b5eb946d913fe58c3986c2af45d22b615ae Mon Sep 17 00:00:00 2001 From: Drew Hamilton Date: Thu, 12 Dec 2024 13:56:25 -0600 Subject: [PATCH] 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. --- poko-annotations/api/poko-annotations.api | 6 ++-- .../drewhamilton/poko/ArrayContentBased.kt | 30 ++++--------------- .../kotlin/dev/drewhamilton/poko/Poko.kt | 27 +++++++++++++++++ .../poko/ir/PokoMembersTransformer.kt | 3 ++ .../drewhamilton/poko/ir/equalsGeneration.kt | 4 ++- .../poko/ir/functionGeneration.kt | 13 ++++---- .../poko/ir/hashCodeGeneration.kt | 13 ++++++-- .../poko/ir/toStringGeneration.kt | 4 ++- 8 files changed, 60 insertions(+), 40 deletions(-) diff --git a/poko-annotations/api/poko-annotations.api b/poko-annotations/api/poko-annotations.api index 1f01e7f5..13465013 100644 --- a/poko-annotations/api/poko-annotations.api +++ b/poko-annotations/api/poko-annotations.api @@ -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 { } diff --git a/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/ArrayContentBased.kt b/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/ArrayContentBased.kt index 879a738f..adc1ff1c 100644 --- a/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/ArrayContentBased.kt +++ b/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/ArrayContentBased.kt @@ -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 diff --git a/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/Poko.kt b/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/Poko.kt index 1f15808c..d33dfc83 100644 --- a/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/Poko.kt +++ b/poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/Poko.kt @@ -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 } diff --git a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/PokoMembersTransformer.kt b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/PokoMembersTransformer.kt index 55bb0b72..f2bb543f 100644 --- a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/PokoMembersTransformer.kt +++ b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/PokoMembersTransformer.kt @@ -45,6 +45,7 @@ internal class PokoMembersTransformer( when { declaration.isEquals() -> declaration.convertToGenerated { properties -> generateEqualsMethodBody( + pokoAnnotation = pokoAnnotationName, context = pluginContext, irClass = declarationParent, functionDeclaration = declaration, @@ -55,6 +56,7 @@ internal class PokoMembersTransformer( declaration.isHashCode() -> declaration.convertToGenerated { properties -> generateHashCodeMethodBody( + pokoAnnotation = pokoAnnotationName, context = pluginContext, functionDeclaration = declaration, classProperties = properties, @@ -64,6 +66,7 @@ internal class PokoMembersTransformer( declaration.isToString() -> declaration.convertToGenerated { properties -> generateToStringMethodBody( + pokoAnnotation = pokoAnnotationName, context = pluginContext, irClass = declarationParent, functionDeclaration = declaration, diff --git a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/equalsGeneration.kt b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/equalsGeneration.kt index b7524da4..298b4053 100644 --- a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/equalsGeneration.kt +++ b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/equalsGeneration.kt @@ -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 @@ -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, @@ -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, diff --git a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/functionGeneration.kt b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/functionGeneration.kt index a25acd32..bcb8e73b 100644 --- a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/functionGeneration.kt +++ b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/functionGeneration.kt @@ -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 /** @@ -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 diff --git a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/hashCodeGeneration.kt b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/hashCodeGeneration.kt index 3dcb19b5..d6c5a225 100644 --- a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/hashCodeGeneration.kt +++ b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/hashCodeGeneration.kt @@ -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 @@ -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, @@ -59,6 +61,7 @@ internal fun IrBlockBodyBuilder.generateHashCodeMethodBody( } else if (classProperties.size == 1) { +irReturn( getHashCodeOfProperty( + pokoAnnotation = pokoAnnotation, context = context, function = functionDeclaration, property = classProperties[0], @@ -83,6 +86,7 @@ internal fun IrBlockBodyBuilder.generateHashCodeMethodBody( ).also { it.parent = functionDeclaration it.initializer = getHashCodeOfProperty( + pokoAnnotation = pokoAnnotation, context = context, function = functionDeclaration, property = classProperties[0], @@ -103,6 +107,7 @@ internal fun IrBlockBodyBuilder.generateHashCodeMethodBody( type = irIntType, dispatchReceiver = shiftedResult, argument = getHashCodeOfProperty( + pokoAnnotation = pokoAnnotation, context = context, function = functionDeclaration, property = property, @@ -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, @@ -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) } } @@ -143,6 +149,7 @@ private fun IrBlockBodyBuilder.getHashCodeOfProperty( */ @OptIn(UnsafeDuringIrConstructionAPI::class) private fun IrBlockBodyBuilder.getHashCodeOf( + pokoAnnotation: ClassId, context: IrPluginContext, property: IrProperty, value: IrExpression, @@ -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)) { diff --git a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/toStringGeneration.kt b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/toStringGeneration.kt index 2893a39e..61b79af9 100644 --- a/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/toStringGeneration.kt +++ b/poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/toStringGeneration.kt @@ -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 @@ -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, @@ -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!!