Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename @ArrayContentBased to @Poko.ReadArrayContent #449

Merged
merged 2 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I correct that there is no binary compatibility concern here because this is a source-retention annotation?

In other words, an app with dependencies on two libraries each compiled with different Poko versions that both use this feature, won't care about the missing dev.drewhamilton.poko.ArrayContentBased type even if it's used to annotate one of the properties in one of the libraries?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep!


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
Loading