Skip to content
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
2 changes: 1 addition & 1 deletion docs/test/ケース05-境界横断.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ IC/ABI 伝播/決定性/binary-swap のビルド駆動面=ケース06 を正典

| ID | 次元/値 | 観測と期待 | 実装 | 備考(関連V/診断) |
|---|---|---|---|---|
| XMP-27 | K12=Java × O1 | Java は Companion static フィールド経由で entries/valueOf/valueOfOrNull と失敗文言を解決・@JvmStatic 非付与のため interface 上に static アクセサが存在しないことを reflection で固定 | JavaCompanionAccessTest#entriesAndValueOfResolveViaCompanionField / #valueOfFailureMessageIsObservable / #noStaticAccessorsExistWithoutJvmStatic | v1 制約の負値固定 |
| XMP-27 | K12=Java × O1 | Java は Companion static フィールド経由で entries/valueOf/valueOfOrNull と失敗文言を解決・@JvmStatic 非付与のため interface 上に static アクセサが存在しないことを reflection で固定 | JavaCompanionAccessTest#entriesAndValueOfResolveViaCompanionField / #valueOfFailureMessageIsObservable / #noStaticAccessorsExistWithoutJvmStatic | 言語制約による非提供の負値固定 |
| XMP-28 | K12=Java/K13=JVM × O1 | 共変 override の bridge がバイトコードに生成され Java から getLabel/getEnumizedClass/getEnumishCompanion/asEnumish が解決 | JvmBytecodeTest#covariantOverridesHaveBridges / JavaCompanionAccessTest#kindMembersResolveViaBridges | — |
| XMP-29 | K12=Java/K13=JVM × O1/O4 | jvmTarget17+ で基底と生成 Enumish に PermittedSubclasses が出力され Java 21 パターン switch が default 無し網羅(値階層・enum 末端階層とも)・生成 kind は Java enum でないため古典 enum switch は構成不能(NG 固定) | JvmBytecodeTest#sealedBaseHasPermittedSubclasses / #generatedEnumishHasPermittedSubclasses / JavaPatternSwitchTest#patternSwitchOverSealedValueIsExhaustive / #patternSwitchCoversEnumLeafHierarchy | V1 |
| XMP-30 | K12=Java × K3=enum × O1 | Java で name()="HELP" と kind getLabel()="Builtin" が管轄別に併存・全定数の asEnumish が同一 companion シングルトン | JavaEnumLeafTest#enumConstantNameAndKindLabelCoexist / #enumConstantsShareTheSingleKind | V4 |
Expand Down
9 changes: 5 additions & 4 deletions docs/コンパイラプラグイン設計00.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,11 @@ entries / valueOf の実体は IR-only の EntriesHolder へ委譲する(IC
生成 Enumish では**抽象のまま**共変 override し(実装は持たない)、各 kind が `KClass<末端の型>` へ絞り込んで実装する
(ボディは IR で充填)。
型パラメータ付きの末端・基底では class リテラルの型は star projection になる(`KClass<Foo<*>>`)。
- **`@JvmStatic` は v1 では付与しない**(Java からの呼び出し形は概要 §3)。
ソース Kotlin では **override メンバーへの `@JvmStatic` が禁止**されており、生成宣言(= override)にも同じ検査が及ぶ
可能性が高い。
この不確実性を v1 に持ち込まないため付与せず、`SI.Enumish.getEntries()` 形の static アクセスは将来拡張とする
- **`@JvmStatic` は付与しない**(Java からの呼び出し形は概要 §3)。
Kotlin は **override メンバーへの `@JvmStatic` を禁止**しており、生成する `entries` / `valueOf` /
`valueOfOrNull` は `EnumishCompanion` の override であるため付与できない。
付与可能にするには `EnumishCompanion` interface を捨てるほかなく(= 共変で束ねる型を失う。概要 §2)、
`SI.Enumish.getEntries()` 形の static アクセスは**提供できない**
(損失は Java からの呼び出し形だけであり、他ターゲットには影響しない)。

## 5. IC 設計
Expand Down
2 changes: 1 addition & 1 deletion docs/コンパイラプラグイン設計01.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ raw で追えない表記は、後続フェーズで解決済み型を見て正
| 対象クラス | 生成するメンバー(シグネチャのみ・ボディ無し) |
|---|---|
| 生成 Enumish | `override val enumishCompanion: SI.Enumish.Companion`(default 実装として宣言。基底の `EnumishCompanion<Enumish>` を宣言側共変により具体型へ絞り込む)/ `override val enumizedClass: KClass<out SI>`(**抽象**として宣言。基底の `KClass<out Enumized<*>>` を共変絞り込みし、実装は各 kind が持つ — ボディはどこにも生成しない。型パラメータ付き基底では `KClass<out S<*>>`) |
| 生成 Enumish の Companion | `override val entries: List<SI.Enumish>` / `override fun valueOf(value: String): SI.Enumish` / `override fun valueOfOrNull(value: String): SI.Enumish?`(**`@JvmStatic` は v1 では付与しない** — 設計00 §4.1。Java からは interface 上の static フィールド `Companion` 経由) |
| 生成 Enumish の Companion | `override val entries: List<SI.Enumish>` / `override fun valueOf(value: String): SI.Enumish` / `override fun valueOfOrNull(value: String): SI.Enumish?`(**`@JvmStatic` は付与しない** — override には付与できない。設計00 §4.1。Java からは interface 上の static フィールド `Companion` 経由) |
| 末端 object / 末端 data object | `override val label: String` / `override val enumizedClass: KClass<末端の型>` / `override fun asEnumish(): <末端の型>` |
| 末端 class(final / data / value class、および **open / abstract class**) | `override fun asEnumish(): <companion の型>`(返り値型は §5.4)。abstract / open の場合も生成先は当該 class 自身であり、**サブタイプはこの実装を継承して同じ kind に吸収される**(設計00 §4.1) |
| 末端 interface / 末端 fun interface | `override fun asEnumish(): <companion の型>` を **default 実装**として生成する(interface に本体つきメンバーを置く。JVM では default メソッドとして lowering される)。fun interface の場合、`Enumized` から継承する抽象 `asEnumish` をこの生成が埋めるため、SAM の抽象メソッドが 1 つに保たれる(ラムダによる SAM 変換がそのまま使えることを全ターゲットで実測確認済み) |
Expand Down
2 changes: 1 addition & 1 deletion docs/コンパイラプラグイン設計02.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ kind の `toString` override は **FIR に宣言を持たず、生成可否の

| ターゲット | 論点 | 方針 |
|---|---|---|
| JVM | interface の default 実装(`enumishCompanion` / 末端 interface の `asEnumish`)の lowering。`enumishCompanion` / `enumizedClass` の共変 override には bridge メソッドが生成される(`@JvmStatic` は v1 では付与しない = 設計00 §4.1) | コンパイラ本体の lowering に委ねる(プラグイン側の追加処理はない)。default 実装の lowering と共変 override の bridge 生成は結合テストのバイトコード観測で実測確認済み |
| JVM | interface の default 実装(`enumishCompanion` / 末端 interface の `asEnumish`)の lowering。`enumishCompanion` / `enumizedClass` の共変 override には bridge メソッドが生成される(`@JvmStatic` は付与しない = 設計00 §4.1) | コンパイラ本体の lowering に委ねる(プラグイン側の追加処理はない)。default 実装の lowering と共変 override の bridge 生成は結合テストのバイトコード観測で実測確認済み |
| JVM | `$EntriesHolder` が Java から見える | `$` を入れた命名と、ABI 保証外であることの文書化(§4.1) |
| JVM (17+) | 基底 sealed の `PermittedSubclasses` 属性 | コンパイラ本体の既存機能である(プラグインは関与しない)。Java 21 の switch との連携は概要 §3 に記載する |
| klib (JS/Native/Wasm) | IR-only 宣言の直列化・同一モジュール内での参照 | §4.1。跨モジュールでの IR 参照はしない |
Expand Down
32 changes: 14 additions & 18 deletions docs/概要.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ Command.Builtin.HELP.label // "Builtin" ← kind の label(Enumized 拡

kind の粒度は「`when (command)` の網羅性の枝」(= sealed の直和の枝を末端まで展開したもの)に一致させており、enum
は全体で 1 kind になる。
enum 定数 1 つずつを kind 化する展開は将来拡張である。
enum 定数 1 つずつを kind 化する展開は提供しない(設計判断) — 展開すると kind と when
の枝が 1 対 1 でなくなり、粒度の一致という前提が崩れるためである。

基底 API が `label` という名前を使うのは、この構成で enum 定数の final メンバー `Enum.name` に横取り・
シャドーイングされないためである(§4)。
Expand Down Expand Up @@ -216,31 +217,30 @@ enum 定数 1 つずつを kind 化する展開は将来拡張である。

## 3. enum 機能との対応カタログ

◎ = 同等以上 / ○ = 生成で同等相当 / △ = 制限付き / × = 提供しない・原理的に不可(`A → B` は「v1 では A、
将来のアドオン等で B になる余地」、`A/B` は「対象・構成により A または B」。
◎ = 同等以上 / ○ = 生成で同等相当 / △ = 制限付き / ×(設計)= 実現手段はあるが設計判断で提供しない /
×(不可)= 言語・実行環境の制約により原理的に実現できない(`A/B` は「対象・構成により A または B」。
本節が「enum と同じにできない部分」の文書化を兼ねる)

| enum の機能 | 可否 | 本プラグインでの姿 |
|---|---|---|
| `entries` | ○ | `SI.Enumish.entries`(`List<T>`。毎回同じインスタンスを返す。`kotlin.enums.EnumEntries` は `E : Enum<E>` という境界を持つため型として使えず、`List<T>` で公開する) |
| `values()` | ○ | `entries` に一本化(enum 側も KT-48872 で entries へ移行済みである) |
| `valueOf(name)` | ○ | `SI.Enumish.valueOf(label)`。加えて `valueOfOrNull`(enum に無い追加) |
| `values()` | △ | 提供しない(`entries` に一本化。enum 側も KT-48872 で entries へ移行済みである) |
| `name` | ○ | `label`(命名理由は §4) |
| `ordinal` | × | **提供しない**(設計判断。§4)。序数が必要な場合は `entries.indexOf(kind)` で得られるが、その値は改名で変動し永続化に使えない(§5) |
| `ordinal` | ×(設計) | **提供しない**(設計判断。§4)。序数が必要な場合は `entries.indexOf(kind)` で得られるが、その値は改名で変動し永続化に使えない(§5) |
| `compareTo`(ordinal 順) | ×(設計) | **提供しない**(`ordinal` 相当を持たないため。§4)。並べ替えが必要な場合は `sortedBy { it.label }` 等、意味の明確な基準を利用側で選ぶ(`entries` の並びは公式な保証の無いコンパイラ提供順であり、改名や階層の入れ子の変更で変動する。§5) |
| `getDeclaringClass` | ○ | `enumishCompanion`(値から階層の Companion へ。「declaring」を名乗らない理由は §2) |
| reified `enumValueOf<T>()` / `enumEntries<T>()` | × | **提供しない**(設計判断。理由は下記)。代替は `SI.Enumish.entries` / `SI.Enumish.valueOf(...)` の直接記述である |
| `compareTo`(ordinal 順) | × | **提供しない**(`ordinal` 相当を持たないため。§4)。並べ替えが必要な場合は `sortedBy { it.label }` 等、意味の明確な基準を利用側で選ぶ(`entries` の並びは公式な保証の無いコンパイラ提供順であり、改名や階層の入れ子の変更で変動する。§5) |
| reified `enumValueOf<T>()` / `enumEntries<T>()` | ×(設計) | **提供しない**(設計判断。理由は下記)。代替は `SI.Enumish.entries` / `SI.Enumish.valueOf(...)` の直接記述である |
| `toString() = name` | ○/△ | 明示的な `toString` 実装(手動宣言・継承・data object の合成)があればそれを尊重し、無ければ `toString() = label` を必ず生成する(末端 enum の kind = companion も同様)。**kind が明示実装なしに `Any` の既定表示へ落ちる構成は許容しない**(§4) |
| 網羅的 `when` | ◎/△ | 値単位の when は sealed の地力で型による網羅性検査とスマートキャストが効く(enum を超える)。kind 単位の when(`when (x.asEnumish())`)は生成 Enumish が sealed のため else 不要(全 kind が呼び出し側から可視の場合に限る。Enumish の手動実装(§8 の許容 = 階層内)がある場合はその枝も必要) |
| `hashCode` / `equals` / 同一性 | ○ | kind は object / companion のシングルトンなので参照同一性が成立する(Java の直列化を経ると壊れる。下記) |
| Java シリアライズ(`java.io.Serializable`) | × | enum は直列化プロトコルがシングルトン性を保証するが、kind は `Serializable` でなく、手動で対応しても `readResolve` なしでは復元後の同一性(when の等値枝の前提)が壊れる |
| `EnumSet` / `EnumMap` | × → △ | Java の本物は `E extends Enum<E>` という境界を持つため使えない。将来 `EnumishSet` / `EnumishMap`(entries 上の位置を内部で利用する実装)を runtime-api で提供する余地はある |
| Java からの利用 | △ | v1 では `@JvmStatic` を付与しないため、Java からは interface 上に公開される static フィールド `Companion` を経由して `SI.Enumish.Companion.getEntries()` / `.valueOf(...)` / `.valueOfOrNull(...)` を呼ぶ(`entries` はプロパティなので Java では `getEntries()` になる)。`@JvmStatic` による `SI.Enumish.getEntries()` 形の static アクセスは**将来拡張**とする(override として生成されるメンバーに付与できるかが未確認であり、その不確実性を v1 に持ち込まないため。設計00 §4.1)。古典的な enum の `switch` は使えない |
| Java シリアライズ(`java.io.Serializable`) | ×(不可) | enum は直列化プロトコルがシングルトン性を保証するが、kind は `Serializable` でなく、手動で対応しても `readResolve` なしでは復元後の同一性(when の等値枝の前提)が壊れる |
| Java からの利用 | △ | `@JvmStatic` を付与しないため、Java からは interface 上に公開される static フィールド `Companion` を経由して `SI.Enumish.Companion.getEntries()` / `.valueOf(...)` / `.valueOfOrNull(...)` を呼ぶ(`entries` はプロパティなので Java では `getEntries()` になる)。`@JvmStatic` による `SI.Enumish.getEntries()` 形の static アクセスは**提供できない** — 生成メンバーは `EnumishCompanion` の override であり、Kotlin は override への `@JvmStatic` を禁止しているためである(設計00 §4.1)。古典的な enum の `switch` は使えない |
| Java 側の網羅 `switch` | ○ | jvmTarget 17+ では sealed に `PermittedSubclasses` 属性が出力され、Java 21 のパターンマッチング switch で網羅性が効く |
| リフレクション連携 | ○ | `KClass.sealedSubclasses` 等の言語標準の機構はそのまま使える(JVM + kotlin-reflect 限定・プラグイン関与なし)。ただし `entries` は中間 sealed を末端まで展開するため、**中間 sealed がある階層では `sealedSubclasses` と要素の集合も並びも一致しない**(§5)。kind からは `enumizedClass` で末端の `KClass` を得られ、reflection 系ライブラリとの接続点になる(§2) |
| kotlinx.serialization | △ | 将来のアドオン `enumizer-serialization`(label ベースの KSerializer)で対応できる。`@Serializable sealed` の既定であるポリモーフィック形式とはワイヤ形状が異なる点に注意 |
| Jackson / Gson / Moshi 等 | × → △ | 各フレームワークは `java.lang.Enum` を特別扱いするため自動では効かない。アダプタを提供する余地はある(将来のアドオン) |
| ORM(Exposed / JPA) | × → △ | `Enum<T>` 境界の API(`enumerationByName` 等)は使えない。label ベースのカスタムマッピングのレシピを文書で提供する |
| kotlinx.serialization | △ | 自動では効かない。label ベースの `KSerializer` を利用側で書く(`@Serializable sealed` の既定であるポリモーフィック形式とはワイヤ形状が異なる点に注意 |
| Jackson / Gson / Moshi 等 | △ | 各フレームワークは `java.lang.Enum` を特別扱いするため自動では効かない。label ベースのカスタムシリアライザを利用側で書く |
| ORM(Exposed / JPA) | △ | `Enum<T>` 境界の API(`enumerationByName` 等)は使えない。label ベースのカスタムマッピングのレシピを文書で提供する |
| Compose 安定性 | ○ | kind はシングルトンの object であり、stable 推論と相性が良い |

### reified ヘルパを提供しない理由
Expand Down Expand Up @@ -335,8 +335,6 @@ enum の `ordinal` に相当するプロパティと、それに基づく `Compa
- sealed 階層の序数は enum のように「宣言順」で定まらない。
コンパイラが提供する継承者順(§5)に従うほかなく、**末端や外側クラスの改名で値が変動する**
- したがって永続化にも通信にも使えず、「使ってはならない値」を API 表面に置くことになる
- 序数を前提とする操作(`EnumSet` 相当の効率的な実装など)は、
将来 runtime-api の内部で `entries` 上の位置を使えば実現でき、公開 API として序数を露出する必要はない
- `Comparable` は序数の存在が前提である(enum の `compareTo` は ordinal 比較)。
序数を持たない以上「何順で比較するのか」の自然な答えが無いため、併せて提供しない。
並べ替えが必要な場合は `sortedBy { it.label }` のように**意味の明確な基準を利用側が選ぶ**方が健全である
Expand Down Expand Up @@ -400,7 +398,6 @@ kind の `toString` は label getter 経由のため自動で追随するが、d

次は将来拡張であり、**v1 では非対応**である。

- 順序の明示指定(§5)
- 生成 Enumish への追加基底 interface の注入(プロパティ / 関数の追加用。生成メンバーの具体 override は許容しない。
手動実装は v1 でも禁止されないため = §8、新たに加わるのは注入の自動化と全 kind への一括適用である)
- 手動 `Enumized<K>` 継承による基底指定の許容([エッジケースへの対応方針](エッジケースへの対応方針.md) §2)
Expand Down Expand Up @@ -451,8 +448,7 @@ kind の `toString` は label getter 経由のため自動で追随するが、d
本プラグインは (a) Kotlin のマイナーバージョン毎のアーティファクト分割(§7)、(b)
順序の回帰を検出する決定性テスト(設計00 §9)によってこれを受容する。
**`entries` 上の位置(`indexOf` の値)を永続化に使ってはならない** — 改名で変動するためである。
永続化には label または独自プロパティを使うこと。
順序の明示指定(`@Enumize(order = ...)` 等)は将来拡張である。
永続化には label または独自プロパティを使うこと。

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;

// Java 消費側: @JvmStatic 非付与(v1)のため、interface 上に公開される static フィールド Companion を
// Java 消費側: @JvmStatic 非付与のため、interface 上に公開される static フィールド Companion を
// 経由して生成 API を呼ぶ(docs/test/ケース05-境界横断.md XMP-27 / XMP-28)
class JavaCompanionAccessTest {
// docs/test/ケース05-境界横断.md XMP-27: Companion フィールド経由の getEntries / valueOf / valueOfOrNull
Expand Down