From d794e89a37cb1867c178da82a73b39c402479fbc Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Mon, 10 Apr 2023 14:28:52 +0100 Subject: [PATCH] Add GenSealedEnum.useIsChecksForSealedObjectComparison (fixes #129) --- .../internal/ksp/GenSealedEnumHolder.kt | 8 +- .../internal/ksp/SealedEnumProcessor.kt | 4 +- .../common/spec/SealedEnumFileSpec.kt | 12 +- .../common/spec/SealedEnumTypeSpec.kt | 13 +- .../sealedenum/compilation/equality/Flag.kt | 120 ++++++++++++++++++ .../compilation/equality/FlagTests.kt | 50 ++++++++ .../internal/processor/SealedEnumProcessor.kt | 4 +- .../com/livefront/sealedenum/GenSealedEnum.kt | 3 +- 8 files changed, 201 insertions(+), 13 deletions(-) create mode 100644 processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/Flag.kt create mode 100644 processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/FlagTests.kt diff --git a/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/GenSealedEnumHolder.kt b/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/GenSealedEnumHolder.kt index fc87bcb0..1df6f120 100644 --- a/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/GenSealedEnumHolder.kt +++ b/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/GenSealedEnumHolder.kt @@ -10,6 +10,7 @@ import com.livefront.sealedenum.TreeTraversalOrder public data class GenSealedEnumHolder( val traversalOrder: TreeTraversalOrder, val generateEnum: Boolean, + val useIsChecksForSealedObjectComparison: Boolean, ) { public companion object { @@ -34,9 +35,14 @@ public data class GenSealedEnumHolder( it.name?.asString() == "generateEnum" }?.value.toString().toBoolean() + val useIsChecksForSealedObjectComparison = ksAnnotation.arguments.find { + it.name?.asString() == "useIsChecksForSealedObjectComparison" + }?.value.toString().toBoolean() + return GenSealedEnumHolder( traversalOrder, - generateEnum + generateEnum, + useIsChecksForSealedObjectComparison ) } } diff --git a/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/SealedEnumProcessor.kt b/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/SealedEnumProcessor.kt index 0dc8ce9f..0085b82f 100644 --- a/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/SealedEnumProcessor.kt +++ b/ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/SealedEnumProcessor.kt @@ -195,9 +195,9 @@ internal class SealedEnumProcessor( sealedEnumOptions = sealedEnumAnnotations.associate { it.traversalOrder to if (it.generateEnum) { @Suppress("UnsafeCallOnNullableType") // Guaranteed safe by above any - SealedEnumWithEnum(sealedClassInterfaces!!) + SealedEnumWithEnum(sealedClassInterfaces!!, it.useIsChecksForSealedObjectComparison) } else { - SealedEnumOnly + SealedEnumOnly(it.useIsChecksForSealedObjectComparison) } } ) diff --git a/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumFileSpec.kt b/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumFileSpec.kt index 6945dda5..989f0b79 100644 --- a/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumFileSpec.kt +++ b/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumFileSpec.kt @@ -76,7 +76,8 @@ public data class SealedEnumFileSpec( parameterizedSealedClass = parameterizedSealedClass, sealedClassCompanionObjectElement = sealedClassCompanionObjectElement, sealedObjects = sealedObjects, - enumPrefix = enumPrefix + enumPrefix = enumPrefix, + useIsChecksForSealedObjectComparison = sealedEnumOption.useIsChecksForSealedObjectComparison ) val sealedEnumClassName = ClassName(sealedClass.packageName, sealedEnumTypeSpecBuilder.name) @@ -243,16 +244,21 @@ public data class SealedEnumFileSpec( * The options for generating classes for a [TreeTraversalOrder]. */ public sealed class SealedEnumOption { + public abstract val useIsChecksForSealedObjectComparison: Boolean + /** * Generate the [SealedEnum] only. */ - public object SealedEnumOnly : SealedEnumOption() + public data class SealedEnumOnly( + override val useIsChecksForSealedObjectComparison: Boolean + ) : SealedEnumOption() /** * Generate the [SealedEnum] and a isomorphic enum class. */ public data class SealedEnumWithEnum( - val sealedClassInterfaces: List + val sealedClassInterfaces: List, + override val useIsChecksForSealedObjectComparison: Boolean ) : SealedEnumOption() } } diff --git a/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumTypeSpec.kt b/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumTypeSpec.kt index e57d6b29..a2385d8e 100644 --- a/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumTypeSpec.kt +++ b/processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumTypeSpec.kt @@ -35,7 +35,8 @@ internal data class SealedEnumTypeSpec( private val parameterizedSealedClass: TypeName, private val sealedClassCompanionObjectElement: TypeElement?, private val sealedObjects: List, - private val enumPrefix: String + private val enumPrefix: String, + private val useIsChecksForSealedObjectComparison: Boolean ) { val name = sealedClass.createSealedEnumName(enumPrefix) private val listOfSealedClass = List::class.asClassName().parameterizedBy(parameterizedSealedClass) @@ -108,7 +109,7 @@ internal data class SealedEnumTypeSpec( } else { beginControlFlow("return when (obj)") sealedObjects.forEachIndexed { index, obj -> - addStatement("%T -> $index", obj) + addStatement(if (useIsChecksForSealedObjectComparison) "is %T -> $index" else "%T -> $index", obj) } endControlFlow() } @@ -131,7 +132,11 @@ internal data class SealedEnumTypeSpec( } else { beginControlFlow("return when (obj)") sealedObjects.forEach { obj -> - addStatement("%T -> %S", obj, sealedObjectToName(obj)) + addStatement( + if (useIsChecksForSealedObjectComparison) "is %T -> %S" else "%T -> %S", + obj, + sealedObjectToName(obj) + ) } endControlFlow() } @@ -177,7 +182,7 @@ internal data class SealedEnumTypeSpec( beginControlFlow("return when (obj)") sealedObjects.forEach { obj -> addStatement( - "%T -> %T", + if (useIsChecksForSealedObjectComparison) "is %T -> %T" else "%T -> %T", obj, enumForSealedEnum.nestedClass(obj.simpleNames.joinToString("_")) ) diff --git a/processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/Flag.kt b/processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/Flag.kt new file mode 100644 index 00000000..cce56d9f --- /dev/null +++ b/processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/Flag.kt @@ -0,0 +1,120 @@ +package com.livefront.sealedenum.compilation.equality + +import com.livefront.sealedenum.GenSealedEnum +import org.intellij.lang.annotations.Language + +sealed class Flag { + val i: Int = 1 shl ordinal + object FirstFlag : Flag() + + object SecondFlag : Flag() + + @GenSealedEnum(generateEnum = true, useIsChecksForSealedObjectComparison = true) + companion object +} + +@Language("kotlin") +val flagGenerated = """ +package com.livefront.sealedenum.compilation.equality + +import com.livefront.sealedenum.EnumForSealedEnumProvider +import com.livefront.sealedenum.SealedEnum +import com.livefront.sealedenum.SealedEnumWithEnumProvider +import kotlin.Int +import kotlin.String +import kotlin.collections.List +import kotlin.reflect.KClass + +/** + * An isomorphic enum for the sealed class [Flag] + */ +public enum class FlagEnum() { + Flag_FirstFlag, + Flag_SecondFlag, +} + +/** + * The isomorphic [FlagEnum] for [this]. + */ +public val Flag.`enum`: FlagEnum + get() = FlagSealedEnum.sealedObjectToEnum(this) + +/** + * The isomorphic [Flag] for [this]. + */ +public val FlagEnum.sealedObject: Flag + get() = FlagSealedEnum.enumToSealedObject(this) + +/** + * An implementation of [SealedEnum] for the sealed class [Flag] + */ +public object FlagSealedEnum : SealedEnum, SealedEnumWithEnumProvider, + EnumForSealedEnumProvider { + public override val values: List = listOf( + Flag.FirstFlag, + Flag.SecondFlag + ) + + + public override val enumClass: KClass + get() = FlagEnum::class + + public override fun ordinalOf(obj: Flag): Int = when (obj) { + is Flag.FirstFlag -> 0 + is Flag.SecondFlag -> 1 + } + + public override fun nameOf(obj: Flag): String = when (obj) { + is Flag.FirstFlag -> "Flag_FirstFlag" + is Flag.SecondFlag -> "Flag_SecondFlag" + } + + public override fun valueOf(name: String): Flag = when (name) { + "Flag_FirstFlag" -> Flag.FirstFlag + "Flag_SecondFlag" -> Flag.SecondFlag + else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) + } + + public override fun sealedObjectToEnum(obj: Flag): FlagEnum = when (obj) { + is Flag.FirstFlag -> FlagEnum.Flag_FirstFlag + is Flag.SecondFlag -> FlagEnum.Flag_SecondFlag + } + + public override fun enumToSealedObject(`enum`: FlagEnum): Flag = when (enum) { + FlagEnum.Flag_FirstFlag -> Flag.FirstFlag + FlagEnum.Flag_SecondFlag -> Flag.SecondFlag + } +} + +/** + * The index of [this] in the values list. + */ +public val Flag.ordinal: Int + get() = FlagSealedEnum.ordinalOf(this) + +/** + * The name of [this] for use with valueOf. + */ +public val Flag.name: String + get() = FlagSealedEnum.nameOf(this) + +/** + * A list of all [Flag] objects. + */ +public val Flag.Companion.values: List + get() = FlagSealedEnum.values + +/** + * Returns an implementation of [SealedEnum] for the sealed class [Flag] + */ +public val Flag.Companion.sealedEnum: FlagSealedEnum + get() = FlagSealedEnum + +/** + * Returns the [Flag] object for the given [name]. + * + * If the given name doesn't correspond to any [Flag], an [IllegalArgumentException] will be thrown. + */ +public fun Flag.Companion.valueOf(name: String): Flag = FlagSealedEnum.valueOf(name) + +""".trimIndent() diff --git a/processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/FlagTests.kt b/processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/FlagTests.kt new file mode 100644 index 00000000..74c16138 --- /dev/null +++ b/processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/equality/FlagTests.kt @@ -0,0 +1,50 @@ +package com.livefront.sealedenum.compilation.equality + +import com.livefront.sealedenum.testing.assertCompiles +import com.livefront.sealedenum.testing.assertGeneratedFileMatches +import com.livefront.sealedenum.testing.compile +import com.livefront.sealedenum.testing.getCommonSourceFile +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class FlagTests { + @Test + fun `two objects sealed class`() { + assertEquals( + listOf(Flag.FirstFlag, Flag.SecondFlag), + FlagSealedEnum.values + ) + } + + @Test + fun `two enums for sealed class`() { + assertEquals( + listOf( + FlagEnum.Flag_FirstFlag, + FlagEnum.Flag_SecondFlag + ), + enumValues().toList() + ) + } + + @Test + fun `two enums for sealed class with mapping`() { + assertEquals( + Flag.values.map(Flag::enum), + enumValues().toList() + ) + } + + @Test + fun `correct enum class`() { + assertEquals(FlagEnum::class, FlagSealedEnum.enumClass) + } + + @Test + fun `compilation generates correct code`() { + val result = compile(getCommonSourceFile("compilation", "equality", "Flag.kt")) + + assertCompiles(result) + assertGeneratedFileMatches("Flag_SealedEnum.kt", flagGenerated, result) + } +} diff --git a/processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/SealedEnumProcessor.kt b/processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/SealedEnumProcessor.kt index c965a0e1..4adc79ca 100644 --- a/processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/SealedEnumProcessor.kt +++ b/processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/SealedEnumProcessor.kt @@ -200,9 +200,9 @@ public class SealedEnumProcessor : AbstractProcessor() { sealedEnumOptions = sealedEnumAnnotations.associate { it.traversalOrder to if (it.generateEnum) { @Suppress("UnsafeCallOnNullableType") // Guaranteed safe by above any call - SealedEnumWithEnum(sealedClassInterfaces!!) + SealedEnumWithEnum(sealedClassInterfaces!!, it.useIsChecksForSealedObjectComparison) } else { - SealedEnumOnly + SealedEnumOnly(it.useIsChecksForSealedObjectComparison) } } ) diff --git a/runtime/src/main/kotlin/com/livefront/sealedenum/GenSealedEnum.kt b/runtime/src/main/kotlin/com/livefront/sealedenum/GenSealedEnum.kt index 6a4d55fd..258c89e7 100644 --- a/runtime/src/main/kotlin/com/livefront/sealedenum/GenSealedEnum.kt +++ b/runtime/src/main/kotlin/com/livefront/sealedenum/GenSealedEnum.kt @@ -201,7 +201,8 @@ package com.livefront.sealedenum @Repeatable public annotation class GenSealedEnum( val traversalOrder: TreeTraversalOrder = TreeTraversalOrder.IN_ORDER, - val generateEnum: Boolean = false + val generateEnum: Boolean = false, + val useIsChecksForSealedObjectComparison: Boolean = false ) /**