From c4963448195708a15c358db6444a4eeff8abb160 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 3 Aug 2026 21:00:01 +0900 Subject: [PATCH 1/5] =?UTF-8?q?docs:=20=E3=82=BB=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E4=BE=8B=E3=81=AE=20Kotlin=20?= =?UTF-8?q?=E3=83=97=E3=83=A9=E3=82=B0=E3=82=A4=E3=83=B3=E3=82=92=20jvm=20?= =?UTF-8?q?=E4=B8=BB=E4=BD=93=E3=81=B8=E5=85=A5=E3=82=8C=E6=9B=BF=E3=81=88?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 利用者の多数派に合わせ kotlin("jvm") を主、multiplatform を注記側とする。 Co-Authored-By: Claude Fable 5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cec136..33aff4a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ operational API of enums on top. ```kotlin plugins { - kotlin("multiplatform") version "2.4.10" // or kotlin("jvm") — any Kotlin target plugin + kotlin("jvm") version "2.4.10" // or kotlin("multiplatform") — any Kotlin target plugin id("io.github.projectmapk.sealed-class-enumizer") version "2.4.10-0.1.0" } ``` From 1e05a466c27d15275abdd2df859dca775d125bba Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 3 Aug 2026 21:00:15 +0900 Subject: [PATCH 2/5] =?UTF-8?q?docs:=20README=20=E3=81=AE=20Status=20?= =?UTF-8?q?=E4=BE=8B=E3=82=92=E3=82=B3=E3=83=B3=E3=83=91=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E5=8F=AF=E8=83=BD=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit data object Deleted が抽象プロパティ remarks を実装しておらず コンパイル不能だったため、抽象宣言をやめ各末端の固有プロパティとする。 Co-Authored-By: Claude Fable 5 --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 33aff4a..c5f8025 100644 --- a/README.md +++ b/README.md @@ -155,10 +155,8 @@ always-available singletons, accepted wherever an enum would have been: ```kotlin @Enumize sealed interface Status { - val remarks: String - - data class Active(override val remarks: String) : Status - data class Suspended(override val remarks: String) : Status + data class Active(val remarks: String) : Status + data class Suspended(val remarks: String) : Status data object Deleted : Status } From 29fc48f89d6afc790d875bc03b892fb65d23eee4 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 3 Aug 2026 21:00:33 +0900 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20README=20=E3=82=92=20Usage=20?= =?UTF-8?q?=E4=B8=BB=E4=BD=93=E3=81=B8=E5=86=8D=E6=A7=8B=E6=88=90=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Motivation 節を廃止し、価値提案の 1 文を導入段落へ吸収。 KT-25871 / sealedSubclasses の制約(R8 で空リスト化 = KT-37292)は Usage の注記 1 文へ縮約 - 基本例へ entries.map { it.enumizedClass } による全末端クラス取得を追加 - ユースケース節を追加: 全ケース列挙(UI・集計軸)・label の永続化往復・ per-kind 配線の網羅検査・kind 起点の値の型を enumizedClass と 突き合わせる全末端テスト Co-Authored-By: Claude Fable 5 --- README.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c5f8025..96a2042 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,10 @@ A Kotlin (K2) compiler plugin that generates enum-like operations — `entries`, `valueOf`, `label` and friends — for `sealed class` / `sealed interface` hierarchies at compile time. +Hierarchies keep their expressive power — data-carrying entries, exhaustive `when` with smart +casts, open leaves — and gain the operational API of enums on top. No runtime reflection involved, working on all Kotlin Multiplatform targets. -## Motivation - -Enumerating the subclasses of a sealed hierarchy without reflection is a long-standing Kotlin -request ([KT-25871](https://youtrack.jetbrains.com/issue/KT-25871)). -The existing answer, `KClass.sealedSubclasses`, is JVM-only, requires `kotlin-reflect`, and breaks -under R8 — impractical for multiplatform and Android projects. - -This plugin fills the gap with compile-time generation: sealed hierarchies keep their expressive -power (data-carrying entries, exhaustive `when` with smart casts, open leaves) and gain the -operational API of enums on top. - ## Setup ### Gradle @@ -118,6 +109,7 @@ sealed interface SI { SI.Enumish.entries // [Bar, Foo] — compiler-provided order SI.Enumish.valueOf("Foo") // label-based lookup; IllegalArgumentException when absent SI.Enumish.valueOfOrNull("X") // null-returning variant (an addition over enums) +SI.Enumish.entries.map { it.enumizedClass } // [Bar::class, Foo::class] — all leaf classes // from a value to its kind val si: SI = SI.Foo(42) @@ -136,6 +128,10 @@ Notes: - `entries` order is the compiler-provided inheritor order (FQN-based), not declaration order. Do not persist positions in the list; persist `label` or a custom property instead. +- `entries.map { it.enumizedClass }` lists every leaf class without reflection — + `KClass.sealedSubclasses` needs the JVM and `kotlin-reflect`, and silently breaks under R8 + ([KT-25871](https://youtrack.jetbrains.com/issue/KT-25871), + [KT-37292](https://youtrack.jetbrains.com/issue/KT-37292)). - `ordinal` and `Comparable` are deliberately not provided: such numbers change on renames and must not be persisted. - Leaves may stay open (`open` / `abstract` class, `interface`, `fun interface`): subtypes defined @@ -180,6 +176,94 @@ This automates the hand-written workaround of giving every leaf a companion that shared marker interface ([background article, Japanese](https://qiita.com/wrongwrong/items/e32179fb851a721007a6)). +### Listing every case + +Data-carrying leaves have no instances to enumerate, so "all statuses" for a picker or a report +axis traditionally means a hand-maintained list — one that silently goes stale when a leaf is +added. +`entries` is compiler-generated and complete by construction: + +```kotlin +// a filter UI offering every status — new leaves show up without touching this code +val statusOptions: List = Status.Enumish.entries.map { it.label } + +// aggregation axes that keep empty groups (groupBy alone would drop them) +val fooCountByStatus: Map = + Status.Enumish.entries.associateWith { 0 } + + foos.groupingBy { it.status.asEnumish() }.eachCount() +``` + +### Round-tripping labels + +Persisting "which case" — a DB column, a query parameter, an analytics event — normally takes a +hand-written string mapping that must follow the hierarchy. +`label` / `valueOf` are that mapping, generated; unlike `value::class.simpleName`, labels are +compile-time constants — never null and unaffected by R8 / minification renaming: + +```kotlin +// outbound: labels are the wire form +fun statusQuery(selection: Set): String = + selection.joinToString("&") { "status=${it.label}" } + +// inbound: GET /foos?status=Active&status=Deleted — parsed kinds feed searchFoo from above +fun handle(rawStatuses: List): List { + val statuses = rawStatuses.map { + requireNotNull(Status.Enumish.valueOfOrNull(it)) { "unknown status: $it" } + } + return repository.searchFoo(*statuses.toTypedArray()) +} +``` + +`@EnumishLabel` keeps persisted labels stable across leaf renames — see +[Label customization](#label-customization). + +### Verifying per-kind wiring + +Not all per-kind wiring fits an exhaustive `when` — handler registries assembled by DI or icon +sets contributed by feature modules live in data, where the compiler cannot check completeness. +`entries` turns "one per leaf" into a single assertion: + +```kotlin +// the map is assembled elsewhere — no single `when` site exists +class StatusRenderer(private val cells: Map) { + init { + val missing = Status.Enumish.entries - cells.keys + require(missing.isEmpty()) { "statuses without a renderer: ${missing.map { it.label }}" } + } +} +``` + +### Exhaustive tests over every leaf + +JUnit's `@EnumSource` has no sealed counterpart, and community substitutes build on +`sealedSubclasses` — JVM-only reflection again. +`entries` drives a test over every leaf on any target, and `enumizedClass` states the expected +type of a value obtained through a kind: + +```kotlin +// production code: a per-kind factory (form defaults, DB seeding, fixtures, …) +fun defaultStatusOf(kind: Status.Enumish): Status = + when (kind) { + Status.Active -> Status.Active(remarks = "") + Status.Suspended -> Status.Suspended(remarks = "payment failed") + Status.Deleted -> Status.Deleted + } + +class DefaultStatusTest { + @Test + fun `every status yields a default of its own type`() { + for (kind in Status.Enumish.entries) { + assertEquals(kind.enumizedClass, defaultStatusOf(kind)::class) + } + } +} +``` + +A new leaf fails the factory's `when` at compile time; a branch fabricating the wrong case fails +the `enumizedClass` assertion. +Values of an open leaf's absorbed subtypes report their runtime class via `::class` — compare +kinds instead (`assertEquals(kind, value.asEnumish())`) in such hierarchies. + ## Generated API Conceptually the plugin generates the following (in compiler internals — no source files are From 741869979661f9663f6b14024b2168a2a363e4e3 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 3 Aug 2026 21:24:19 +0900 Subject: [PATCH 4/5] =?UTF-8?q?docs:=20=E3=82=BB=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=81=AE=20Maven=20=E7=AF=80?= =?UTF-8?q?=E3=82=92=E6=8A=98=E3=82=8A=E3=81=9F=E3=81=9F=E3=82=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 利用者が少ない想定のため details 化し、既定表示を Gradle 中心とする。 Co-Authored-By: Claude Fable 5 --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 96a2042..cb0b6d3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ sealedClassEnumizer { } ``` -### Maven +
+Maven Declare the plugin as a dependency of kotlin-maven-plugin and name it in `compilerPlugins`; the compiler plugin follows as a transitive dependency. It applies to every kotlin-maven-plugin @@ -79,6 +80,8 @@ The project-wide default label case is a property: The same option can be given through kotlin-maven-plugin's `pluginOptions` (`sealed-class-enumizer:labelCase=...`), which takes precedence over the property. +
+ ### IntelliJ IDEA IntelliJ's K2 mode does not load third-party compiler plugins by default, so generated From 6fc10b7aab0dabf9ba1f47233dc800fda0305e14 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 3 Aug 2026 21:24:34 +0900 Subject: [PATCH 5/5] =?UTF-8?q?docs:=20IntelliJ=20=E3=81=AE=20Registry=20?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E4=BE=8B=E3=82=B9=E3=82=AF=E3=83=AA=E3=83=BC?= =?UTF-8?q?=E3=83=B3=E3=82=B7=E3=83=A7=E3=83=83=E3=83=88=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kotlin.k2.only.bundled.compiler.plugins.enabled を無効化した Registry ダイアログを assets へ追加し、Setup の手順文へ挿入する。 Co-Authored-By: Claude Fable 5 --- README.md | 5 ++++- assets/intellij-registry-flag.png | Bin 0 -> 16233 bytes 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 assets/intellij-registry-flag.png diff --git a/README.md b/README.md index cb0b6d3..39329de 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,10 @@ declarations show as unresolved in the editor even though Gradle builds succeed ([KTIJ-29248](https://youtrack.jetbrains.com/issue/KTIJ-29248)). To make resolution and completion work, disable the registry flag `kotlin.k2.only.bundled.compiler.plugins.enabled` (Help | Find Action… | "Registry…"), then -re-sync the project. +re-sync the project: + +![The Registry dialog with `kotlin.k2.only.bundled.compiler.plugins.enabled` unchecked](assets/intellij-registry-flag.png) + This IDE capability is experimental; command-line and Gradle builds are unaffected either way. Once the flag is disabled, IntelliJ resolves the generated declarations and renders them inline: diff --git a/assets/intellij-registry-flag.png b/assets/intellij-registry-flag.png new file mode 100644 index 0000000000000000000000000000000000000000..eae00ac3c82727f9273388364ffe687cfdd3f6dd GIT binary patch literal 16233 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 literal 0 HcmV?d00001