diff --git "a/docs/test/\343\202\261\343\203\274\343\202\27105-\345\242\203\347\225\214\346\250\252\346\226\255.md" "b/docs/test/\343\202\261\343\203\274\343\202\27105-\345\242\203\347\225\214\346\250\252\346\226\255.md" index 1ad9d3b..f70423b 100644 --- "a/docs/test/\343\202\261\343\203\274\343\202\27105-\345\242\203\347\225\214\346\250\252\346\226\255.md" +++ "b/docs/test/\343\202\261\343\203\274\343\202\27105-\345\242\203\347\225\214\346\250\252\346\226\255.md" @@ -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 | diff --git "a/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21000.md" "b/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21000.md" index 9172fca..4be476f 100644 --- "a/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21000.md" +++ "b/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21000.md" @@ -192,10 +192,11 @@ entries / valueOf の実体は IR-only の EntriesHolder へ委譲する(IC 生成 Enumish では**抽象のまま**共変 override し(実装は持たない)、各 kind が `KClass<末端の型>` へ絞り込んで実装する (ボディは IR で充填)。 型パラメータ付きの末端・基底では class リテラルの型は star projection になる(`KClass>`)。 -- **`@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 設計 diff --git "a/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21001.md" "b/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21001.md" index 533ba00..8cf15f1 100644 --- "a/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21001.md" +++ "b/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21001.md" @@ -228,7 +228,7 @@ raw で追えない表記は、後続フェーズで解決済み型を見て正 | 対象クラス | 生成するメンバー(シグネチャのみ・ボディ無し) | |---|---| | 生成 Enumish | `override val enumishCompanion: SI.Enumish.Companion`(default 実装として宣言。基底の `EnumishCompanion` を宣言側共変により具体型へ絞り込む)/ `override val enumizedClass: KClass`(**抽象**として宣言。基底の `KClass>` を共変絞り込みし、実装は各 kind が持つ — ボディはどこにも生成しない。型パラメータ付き基底では `KClass>`) | -| 生成 Enumish の Companion | `override val entries: List` / `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` / `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(): `(返り値型は §5.4)。abstract / open の場合も生成先は当該 class 自身であり、**サブタイプはこの実装を継承して同じ kind に吸収される**(設計00 §4.1) | | 末端 interface / 末端 fun interface | `override fun asEnumish(): ` を **default 実装**として生成する(interface に本体つきメンバーを置く。JVM では default メソッドとして lowering される)。fun interface の場合、`Enumized` から継承する抽象 `asEnumish` をこの生成が埋めるため、SAM の抽象メソッドが 1 つに保たれる(ラムダによる SAM 変換がそのまま使えることを全ターゲットで実測確認済み) | diff --git "a/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21002.md" "b/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21002.md" index 87f63fc..f9ccca1 100644 --- "a/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21002.md" +++ "b/docs/\343\202\263\343\203\263\343\203\221\343\202\244\343\203\251\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\350\250\255\350\250\21002.md" @@ -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 参照はしない | diff --git "a/docs/\346\246\202\350\246\201.md" "b/docs/\346\246\202\350\246\201.md" index 9a203b4..0531274 100644 --- "a/docs/\346\246\202\350\246\201.md" +++ "b/docs/\346\246\202\350\246\201.md" @@ -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)。 @@ -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`。毎回同じインスタンスを返す。`kotlin.enums.EnumEntries` は `E : Enum` という境界を持つため型として使えず、`List` で公開する) | +| `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()` / `enumEntries()` | × | **提供しない**(設計判断。理由は下記)。代替は `SI.Enumish.entries` / `SI.Enumish.valueOf(...)` の直接記述である | -| `compareTo`(ordinal 順) | × | **提供しない**(`ordinal` 相当を持たないため。§4)。並べ替えが必要な場合は `sortedBy { it.label }` 等、意味の明確な基準を利用側で選ぶ(`entries` の並びは公式な保証の無いコンパイラ提供順であり、改名や階層の入れ子の変更で変動する。§5) | +| reified `enumValueOf()` / `enumEntries()` | ×(設計) | **提供しない**(設計判断。理由は下記)。代替は `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` という境界を持つため使えない。将来 `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` 境界の API(`enumerationByName` 等)は使えない。label ベースのカスタムマッピングのレシピを文書で提供する | +| kotlinx.serialization | △ | 自動では効かない。label ベースの `KSerializer` を利用側で書く(`@Serializable sealed` の既定であるポリモーフィック形式とはワイヤ形状が異なる点に注意) | +| Jackson / Gson / Moshi 等 | △ | 各フレームワークは `java.lang.Enum` を特別扱いするため自動では効かない。label ベースのカスタムシリアライザを利用側で書く | +| ORM(Exposed / JPA) | △ | `Enum` 境界の API(`enumerationByName` 等)は使えない。label ベースのカスタムマッピングのレシピを文書で提供する | | Compose 安定性 | ○ | kind はシングルトンの object であり、stable 推論と相性が良い | ### reified ヘルパを提供しない理由 @@ -335,8 +335,6 @@ enum の `ordinal` に相当するプロパティと、それに基づく `Compa - sealed 階層の序数は enum のように「宣言順」で定まらない。 コンパイラが提供する継承者順(§5)に従うほかなく、**末端や外側クラスの改名で値が変動する** - したがって永続化にも通信にも使えず、「使ってはならない値」を API 表面に置くことになる -- 序数を前提とする操作(`EnumSet` 相当の効率的な実装など)は、 - 将来 runtime-api の内部で `entries` 上の位置を使えば実現でき、公開 API として序数を露出する必要はない - `Comparable` は序数の存在が前提である(enum の `compareTo` は ordinal 比較)。 序数を持たない以上「何順で比較するのか」の自然な答えが無いため、併せて提供しない。 並べ替えが必要な場合は `sortedBy { it.label }` のように**意味の明確な基準を利用側が選ぶ**方が健全である @@ -400,7 +398,6 @@ kind の `toString` は label getter 経由のため自動で追随するが、d 次は将来拡張であり、**v1 では非対応**である。 -- 順序の明示指定(§5) - 生成 Enumish への追加基底 interface の注入(プロパティ / 関数の追加用。生成メンバーの具体 override は許容しない。 手動実装は v1 でも禁止されないため = §8、新たに加わるのは注入の自動化と全 kind への一括適用である) - 手動 `Enumized` 継承による基底指定の許容([エッジケースへの対応方針](エッジケースへの対応方針.md) §2) @@ -451,8 +448,7 @@ kind の `toString` は label getter 経由のため自動で追随するが、d 本プラグインは (a) Kotlin のマイナーバージョン毎のアーティファクト分割(§7)、(b) 順序の回帰を検出する決定性テスト(設計00 §9)によってこれを受容する。 **`entries` 上の位置(`indexOf` の値)を永続化に使ってはならない** — 改名で変動するためである。 -永続化には label または独自プロパティを使うこと。 -順序の明示指定(`@Enumize(order = ...)` 等)は将来拡張である。 +永続化には label または独自プロパティを使うこと。 --- diff --git a/integration-test/java-consumer/src/test/java/io/github/projectmapk/javaconsumer/JavaCompanionAccessTest.java b/integration-test/java-consumer/src/test/java/io/github/projectmapk/javaconsumer/JavaCompanionAccessTest.java index 1a4d81b..d902d2d 100644 --- a/integration-test/java-consumer/src/test/java/io/github/projectmapk/javaconsumer/JavaCompanionAccessTest.java +++ b/integration-test/java-consumer/src/test/java/io/github/projectmapk/javaconsumer/JavaCompanionAccessTest.java @@ -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