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
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,22 @@ class EnumizeIrBaseGenerator(private val ctx: EnumizeIrContext) {
}

// 末端の列挙(docs/コンパイラプラグイン設計02.md §2): コンパイラの継承者リスト(FQN 順に正規化済み)を走査し、
// 中間 sealed に到達したらその継承者リストへ再帰的に降りる。並べ替えは一切行わない(docs/コンパイラプラグイン設計02.md §3)
// 中間 sealed に到達したらその継承者リストへ再帰的に降りる。並べ替えは一切行わない(docs/コンパイラプラグイン設計02.md §3)。
// 中間 sealed を含む全メンバーを走査順を保つ集合へ初出のみ入れ、そこから末端だけを取り出す。
// 複数の中間 sealed を同時に実装するメンバーは経路の数だけ到達するため、この集合が
// 末端の重複掲載(kind の集合が壊れる)と中間の再展開(同じ部分木の再走査)を同時に抑える
private fun collectLeaves(base: IrClass): List<IrClass> {
val result = mutableListOf<IrClass>()
collectLeavesInto(base, result, LinkedHashSet())
return result
val members = LinkedHashSet<IrClass>()
collectMembersInto(base, members)
return members.filterNot { it.modality == Modality.SEALED }
}

private fun collectLeavesInto(
current: IrClass,
result: MutableList<IrClass>,
visited: MutableSet<IrClass>,
) {
if (!visited.add(current)) return
private fun collectMembersInto(current: IrClass, members: MutableSet<IrClass>) {
for (subclassSymbol in current.sealedSubclasses) {
val subclass = subclassSymbol.owner
if (!members.add(subclass)) continue
if (subclass.modality == Modality.SEALED) {
collectLeavesInto(subclass, result, visited)
} else {
result.add(subclass)
collectMembersInto(subclass, members)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/test/ケース01-生成と実行時API.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ K9 の表記変種(import 別名・FQN・star import・typealias)と診断
| API-29 | K5=1 段 × K9=中間経由再帰追跡 | 中間 sealed class / sealed interface 経由の raw 追跡再帰で末端 companion 自動生成 | MidTrackingTest#leavesViaIntermediatesGetAutoCompanions | V3 |
| API-30 | K5=中間非生成 | 中間には何も生成されず entries 非掲載(末端まで平坦化)・中間の明示 companion へも Enumish 非注入(kind 非成立)・中間型変数の asEnumish は末端 kind へ実体解決 | MidTrackingTest#intermediatesHaveNoKind | 多段の展開順→ケース03 |
| API-51 | K9=スコープ順の競合解決 | raw 追跡の優先関係を競合 3 形で固定: star import 基底 vs 同一 pkg 同名非基底=同一 pkg 勝ち(entries 非所属)・同一 pkg 同名 vs 明示 import 基底=import 勝ち(所属)・明示 import vs 外側ネスト同名=ネスト勝ち(非所属) | RawTrackingTest#scopePriorityDecidesMembership | V3。表記単独形の成立→ケース04 DIA-31/32 |
| API-57 | K5=多重経路(兄弟中間の同時実装) | 複数経路で到達する末端は初出位置に 1 回だけ entries 掲載・kind は 1 つ(どちらの中間型からも同じ kind へ解決)・生成 Enumish の継承者一覧も重複せず kind-when は else 不要 | MultiPathTest#multiPathLeafIsListedOnceAtFirstOccurrence / MultiPathTest#multiPathLeafHasSingleKind | 基底は一意のため診断は非発火。展開順の規則→ケース03 §1 |

## 8. 型パラメータ(generic・K11)

Expand Down
1 change: 1 addition & 0 deletions docs/test/ケース03-順序.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ FQN UTF-16 序数と DFS 入れ子展開に関する順序観測は本資料へ
- 順序 = コンパイラの sealed 継承者リスト走査順(setSealedClassInheritors セッター側で ClassId FQN 文字列の UTF-16 序数昇順に正規化・大小区別・ロケール非依存)。
- プラグインは FIR / IR とも並べ替えに一切介入しない(設計00 §6.2・設計02 §3)。
- 中間 sealed はその位置で継承者リストを深さ優先に入れ子展開し、entries には末端のみが載る。
- 複数経路で到達する末端(兄弟中間の同時実装)は初出位置に 1 回だけ載る(設計00 §6.2・設計02 §2。観測はケース01 API-57)。
- 整列キーは末端の ClassId のみ(kind が companion でも companion 名は整列に関与しない)。

## 2. 期待列の導出
Expand Down
2 changes: 1 addition & 1 deletion docs/test/テスト戦略.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Kotlin 言語仕様がこのプラグインに対して張る空間を次の次
| K2 | 基底の修飾 | expect / actual(共にエラー)・inner(sealed 不能=NOT_SEALED 合流)・型パラメータ付き・ネスト配置・可視性 |
| K3 | 末端の宣言種別 | object / data object / class / data class / value class / enum class / open class / abstract class / interface / fun interface / 階層外クラスの companion(既定名・名前付き) |
| K4 | 末端の修飾 | inner(エラー)・型パラメータ付き・可視性・expect/actual(言語委譲) |
| K5 | 中間 sealed | 無 / 1 段 / 多段 / 兄弟複数 / 継承者ゼロ / 可視性 / 配置(ネスト・トップレベル) |
| K5 | 中間 sealed | 無 / 1 段 / 多段 / 兄弟複数 / 多重経路(兄弟中間の同時実装) / 継承者ゼロ / 可視性 / 配置(ネスト・トップレベル) |
| K6 | companion 状態 | 無し(自動生成) / 明示既定名 / 明示名前付き / 可視性 4 値 / ユーザーメンバー共存 / companion 自身が末端(外側=末端: エラー・外側=基底/中間: 成立・外側=階層外: 許容) / 非 companion のネスト宣言 `Companion` との衝突(実挙動固定) |
| K7 | 配置 | 基底ネスト / 同一ファイルトップレベル / 別ファイルトップレベル / 無関係クラス内ネスト / private 外側クラス内 / 非 sealed クラス内の基底 / 多段ネスト / local(関数内。実装は FIR・チェッカー・IR の全入口で一貫除外=無診断・無生成の素通りを固定する) / 別パッケージ(sealed の言語制約エラーへ委譲) |
| K8 | 可視性 | (基底/中間/末端/companion) × (public/internal/protected/private) → 実効可視性・asEnumish 3 段規則・IR-only アクセサ壁 3 形・基底より広い末端(E-2) |
Expand Down
2 changes: 1 addition & 1 deletion docs/test/フィクスチャ構成.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ src/test には test compilation 配線検証用の小さな @Enumize 階層(T
| companionleaf | Token = sealed interface + Host.Companion(既定名 companion 末端) + WithNamed.Named(名前つき companion 末端) + data object HostA + Holder2.Zzz(名前つき・順序プローブ)。基底/中間 companion 末端の成立形は sealedbase / mid が担う | K3=companion 末端(外側=階層外)・label=宣言名・'.'(46) 序数境界 | API/ORD |
| sealedbase | Task = sealed class + data class Run(`:Task()`・自動 companion) + data object Done + object Plain + protected class Inner(基底ネスト・明示 companion・基底 companion に factory と asEnumish 型付き観測メンバー=規則 1 の基底スコープ内観測) + 基底自身の companion(`companion object : Task()`)=基底 companion 末端の成立形(label="Companion") | K1=sealed class・K9 コンストラクタ呼び出し形・protected 末端・K6 基底 companion 末端 | API/VIS |
| generic | `Generic<T>` = sealed class + data class `Box<T>` + class `Empty<T>` + 非 generic 末端 class Fixed(`:Generic<Unit>()`) ‖ Holder = 非 generic sealed interface + generic 末端 data class `Cell<out T>`(変位注釈) | K11: 型パラメータ(両方/基底のみ/末端のみ/変位)・star projection・kind の型引数非依存 | API |
| mid | RootVia = sealed interface + 中間 sealed class MidClass(companion object : RootVia=中間 companion 末端の成立形) + 中間 sealed interface MidIface(明示 companion=非実装・Enumish 非注入・kind 非成立の観測) + 各経由の companion 無し末端 | K5×K9×K6: 中間経由の raw 追跡再帰(V3)・中間へ非生成・中間 companion の非注入/末端成立の両形 | API |
| mid | RootVia = sealed interface + 中間 sealed class MidClass(companion object : RootVia=中間 companion 末端の成立形) + 中間 sealed interface MidIface(明示 companion=非実装・Enumish 非注入・kind 非成立の観測) + 各経由の companion 無し末端 ‖ MultiPath = 兄弟中間 MpLeft / MpRight + 両方を実装する末端 MpBoth(自動 companion) + 各単独経路末端 MpOnlyLeft / MpOnlyRight(RootVia の entries スナップショットを汚さないよう独立階層とする) | K5×K9×K6: 中間経由の raw 追跡再帰(V3)・中間へ非生成・中間 companion の非注入/末端成立の両形・多重経路の初出 1 回掲載 | API |
| scope | 真基底 scope.target.Holder.Base(@Enumize・object Holder 内ネスト)+囮トップレベル scope.target.Base(非 sealed interface)+別 pkg 実基底 scope.other.Base(@Enumize)。競合 3 形(末端は全て scope.target 配置): (1) 明示 import Holder.Base vs 同一 pkg 囮=import 勝ち→所属 (2) scope.other.* star import vs 同一 pkg 囮=同一 pkg 勝ち→非所属・コンパイル成功 (3) 明示 import vs ホストの内側ネスト同名=ネスト勝ち→非所属 | K9: raw 追跡スコープ順の優先関係を entries 所属/非所属+コンパイル成立で固定 | API |
| order.flat | FlatRoot = sealed interface + 文字境界末端(AB1/Ab2/aB3/ab4・A1/AA/A_/Az)+ 配置末端(基底ネスト Aaa・無関係クラス Box.Bbb・トップレベル Mmm/Zzz/aLower)+ 接頭辞対(Foo=class 自動 companion / FooBar=data object)+ セパレータ対(ネスト Sep.Q / トップレベル Sep0) | K7×O3: FQN UTF-16 序数の全境界を 1 スナップショット固定 | ORD |
| order.mid | MidRoot = sealed interface + MidNest{NestA,NestB}(トップレベル中間×ネスト末端=FQN 順一致) + MidTop{Early,Late}(トップレベル中間×トップレベル末端=break) + MidMulti.Deep{Bottom,Wide}(多段) + MidRoot.MidIn{Outpost}(基底ネスト中間×トップレベル末端) + 直接末端 Aaa/Zzz(in-place 展開の前後) + Probe=class 末端(自動 companion)+その内側ネスト data object 末端 Bb(単純名が Companion より序数で小=entries 順と inheritors 順の分岐プローブ) | K5×O3: DFS 入れ子展開・break・多段・中間配置 2 形・inheritors 分岐を 1 スナップショット固定 | ORD |
Expand Down
3 changes: 3 additions & 0 deletions docs/コンパイラプラグイン設計00.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ entries = [Bbb, Aaa]
末端が中間 sealed の内側にネストしている通常の構成では、ネストした末端の FQN が中間の FQN を接頭辞に持つため結果的に
FQN 序数順と一致する

- **同じ末端へ複数経路で到達する構成**(兄弟の中間 sealed を同時に実装する末端)では、
**初出位置に 1 回だけ載る**。
重複の除去は並べ替えではなく、entries が kind の集合であることの帰結である
- 生成 Enumish の inheritors 属性(§5.2)も同様に、収集した順のまま登録する。
属性へ格納される時点でセッター側の正規化(§6.1)が働くため、プラグイン側で整える必要はない

Expand Down
2 changes: 2 additions & 0 deletions docs/コンパイラプラグイン設計02.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ IR 側の責務は次の 3 つである:
り、**真実の単一ソース**である(仮に IC 下でこの集合が壊れるとすれば、それは言語機能側のバグであり、
プラグイン固有の破損ではない)
- 中間 sealed は再帰的に展開し、**末端のみ**を集める(中間 sealed 自身は entries に載せない)
- 複数の中間 sealed を同時に実装する末端は経路の数だけ到達するため、**初出位置の 1 回だけを残す**。
ここで列挙するのは kind の**集合**であり、重複は enum 的意味論(末端 1 種類につき 1 kind)を壊す
- 各末端から kind を解決する: object なら末端自身の IrClass、
それ以外(class / enum class / interface。open / abstract を含む)ならその companion の IrClass
- ここで列挙するのは**基底 SI の階層 = kind の集合**である。
Expand Down
3 changes: 3 additions & 0 deletions docs/概要.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,9 @@ kind の `toString` は label getter 経由のため自動で追随するが、d
末端が中間 sealed の内側にネストしている通常の構成では、ネストした末端の FQN が中間の FQN を接頭辞に持つため、
結果的に FQN 序数順と一致する

- **末端が複数の中間 sealed を同時に実装している場合**(`class C : Mid1, Mid2`)は、
`entries` には**最初に到達した位置に 1 回だけ**現れる(kind は末端 1 種類につき 1 つである)

**性質(enum の宣言順との違い)**:

- **宣言順ではない**。宣言を並べ替えても `entries` の並びは変わらない
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.projectmapk.fixtures.mid

// 2 つの中間 sealed を同時に実装する末端(companion 明示なし = 多重経路での候補判定も観測する)。
// MpLeft 経由の初出位置に 1 回だけ entries へ載る(docs/test/ケース01-生成と実行時API.md API-57)
class MpBoth : MpLeft, MpRight
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.projectmapk.fixtures.mid

// 兄弟中間の一方(多重経路末端 MpBoth の初出経路。docs/test/ケース01-生成と実行時API.md API-57)
sealed interface MpLeft : MultiPath
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.projectmapk.fixtures.mid

// MpLeft 単独経路の末端(MpBoth の後続 = 初出位置の対照。docs/test/ケース01-生成と実行時API.md API-57)
data object MpOnlyLeft : MpLeft
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.projectmapk.fixtures.mid

// MpRight 単独経路の末端(MpBoth の重複が除かれた後に続く。docs/test/ケース01-生成と実行時API.md API-57)
data object MpOnlyRight : MpRight
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.projectmapk.fixtures.mid

// 兄弟中間の他方(多重経路末端 MpBoth の 2 度目の到達経路。docs/test/ケース01-生成と実行時API.md API-57)
sealed interface MpRight : MultiPath
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.projectmapk.fixtures.mid

import io.github.projectmapk.sealedClassEnumizer.Enumize

// 多重経路(複数の中間 sealed を同時に実装する末端)のフィクスチャ基底
// (docs/test/ケース01-生成と実行時API.md API-57)。
// 継承者はファイル分散: MpLeft.kt / MpRight.kt(兄弟中間)と各末端ファイル。
// RootVia とは独立した階層とし、entries スナップショットを相互に汚さない
@Enumize sealed interface MultiPath
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.github.projectmapk.fixtures.mid

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertSame

// 多重経路(複数の中間 sealed を同時に実装する末端)の box テスト
// (docs/test/ケース01-生成と実行時API.md §7)
class MultiPathTest {
// docs/test/ケース01-生成と実行時API.md API-57: 複数経路で到達する末端は初出位置に 1 回だけ載る。
// 直接継承者の整列 [MpLeft, MpRight] を DFS 展開すると MpLeft 側で [MpBoth, MpOnlyLeft]・
// MpRight 側で [MpBoth(重複), MpOnlyRight] となり、2 度目の MpBoth は落ちる
@Test
fun multiPathLeafIsListedOnceAtFirstOccurrence() {
assertEquals(
listOf("MpBoth", "MpOnlyLeft", "MpOnlyRight"),
MultiPath.Enumish.entries.map { it.label },
)
}

// docs/test/ケース01-生成と実行時API.md API-57: 経路が複数でも kind は 1 つで、
// どちらの中間型からも同じ kind へ解決される。生成 Enumish の継承者一覧も重複せず
// kind-when は else 不要で網羅する
@Test
fun multiPathLeafHasSingleKind() {
val viaLeft: MpLeft = MpBoth()
val viaRight: MpRight = MpBoth()
assertSame(MpBoth.Companion, viaLeft.asEnumish())
assertSame(MpBoth.Companion, viaRight.asEnumish())
assertSame(MpBoth.Companion, MultiPath.Enumish.valueOf("MpBoth"))
assertEquals(
listOf("both", "left", "right"),
MultiPath.Enumish.entries.map { kind ->
when (kind) {
MpBoth.Companion -> "both"
MpOnlyLeft -> "left"
MpOnlyRight -> "right"
}
},
)
}
}