コンテンツにスキップ

ADR-36 — Macro-substrate nested-class emission tier (Mangrove `Enum`)

ステータス: Accepted, 2026-05-30; Slice A implemented.

ADR-16のマクロ展開基層(substrate)を、クラスレベルのDSLブロックから(メソッドだけでなく)ネストしたサブクラスを生成する新しいティアで拡張するという決定を記録する。動機となったのはMangroveEnum DSLである。Mangroveサポートのうち出荷可能で契約(contract)の範囲内にある部分(アンラップ呼び出しサイトでのキャリア(carrier)ジェネリックなインスタンス化)はplugins/rigor-mangroveとして別途ランディングした。このADRは、今日のプラグイン契約が表現できなかった部分をスコープする。

Slice A実装済み(2026-05-30): Plugin::Macro::NestedClassTemplate(マニフェストスロットnested_class_templates:)とSyntheticMethodScanner内のスキャナパスを追加した。これは、receiver_constraintextendするクラス上の<block_method> do … endブロック内の各variant <Const>, <Type>行について、バリアントサブクラス名を記録し(よってEnvironment#class_known?がそれを解決する → 定数参照 + meta_new経由の.newディスパッチ)、既存のSyntheticMethodIndexを通じてリテラル定数のペイロード型を返す#innerリーダーを合成する。Mangrove::Enumのためにrigor-mangroveへ配線した。Shape::Circle.new(1.0).innerはいまやFloatと型付けされる。保留(WD3の上限): sealedな親ファクト(fact)+ is_a?によるバリアント横断の網羅的ナローイング(narrowing)。これには合成クラスの階層をEnvironment#class_orderingへスレッディングする必要がある。また、非定数な内部シェイプ(shape、シェイプハッシュ)も保留で、今日はDynamic[Top]へ退化する。

基礎調査: docs/notes/20260530-mangrove-library-survey.md

MangroveのEnumは代数的データ型のDSLである:

class Shape
extend Mangrove::Enum
variants do
variant Circle, Float
variant Rectangle, { width: Float, height: Float }
variant Unit, NilClass
end
end

variant Const, Type宣言は、#inner : <Type>を担うネストしたサブクラスShape::Circle < Shapeを生成する(加えて固定のメソッド集合 — inneras_superserialize==)ことを意図している。下流のコードは、それらのバリアントを構築しマッチする:

shape = Shape::Circle.new(1.0)
case shape
when Shape::Circle then shape.inner.floor # inner : Float
end

なぜ現在の契約サーフェスが合わないか

Section titled “なぜ現在の契約サーフェスが合わないか”

Mangroveはこれをconst_missing + 文字列化されたRubyヒアドキュメントのclass_evalで実装している — バリアントサブクラスはShape::Circleが初めて参照されたときに遅延的に定義される(調査メモ§「Enum DSL」)。生成されるサーフェスはソースから完全に回復可能である — variant Const, Typeの対はリテラル引数であり、emitされるメソッド集合は固定である — が、v0.1.xのどの基層ティアもそれをemitできない:

  • ADR-16のティアC(Macro::HeredocTemplatesymbol_arg_positionでリテラルSymbolを抽出し、呼び出し元クラス上のメソッドをemitする。Mangroveのvariant定数(バリアントクラス名)を取り、emit先はShape上のメソッドではなく新しいネストクラスである。rigor-dry-structはすでにこの正確なギャップを文書化している: 「ネストブロック形式(attribute :details do ... endAddress::Detailsを生成する)はスコープ外 …… そのパターンはティアA + ティアCの合成 + const_setによるemitを必要とする。保留。」
  • ティアA(BlockAsMethodはブロック本体のself_typeを再ターゲットする。定数は生成しない。
  • ティアB(TraitRegistryはシンボルを既存のモジュールへ写してincludeする。型は作らない。

基層の下限(ADR-16のWD13)は「合成メソッドを名前でemitする」である。「合成クラスemit」のプリミティブは存在しない。だからMangroveのEnumはその標準的な動機付けケースである。

調査からのもう2つのMangroveサーフェスは、このADRでは明示的にスコープ外である:

  • sealedなバリアント集合に対するis_a?(Shape::Circle)の網羅的ナローイングは、コアの制御フロー解析であり(型の宇宙が知るどのsealed階層であれそれを消費する)、基層の関心事ではない。これは、このADRのemitティアが、バリアントが存在することとShapeがそのsealedな親であることを宇宙に教えたときに有用になるが、ナローイング自体はエンジンの仕事である。
  • キャリアジェネリックなインスタンス化(unwrap!OkType)はplugins/rigor-mangroveで出荷済みで、ここでは何も必要としない。

ADR-16の基層にネストクラスemitティアを追加する。作業上の形(実装中の精緻化を要する):

nested_class_templates: [
Rigor::Plugin::Macro::NestedClassTemplate.new(
receiver_constraint: "Mangrove::Enum", # extend-side marker
block_method: :variants, # the enclosing DSL block
variant_method: :variant, # each declaration call
name_arg_position: 0, # constant arg → nested class name
inner_arg_position: 1, # type arg → #inner return
superclass: :enclosing, # Shape::Circle < Shape
emit: [
{ name: "inner", returns_from_arg: { position: 1 } },
{ name: "as_super", returns: :enclosing },
{ name: "==", returns: "bool" }
],
sealed: true # mark the parent sealed
)
]

実装前にADR本文で確定すべき作業上の決定:

  • WD1 — emitプリミティブ。プリパスは、各variant Const, Type行について、囲むクラスを親とする名前的型(nominal type、公称型とも)<<Enclosing>>::<<Const>>を合成し、ディスパッチャーがすでに参照する同じSyntheticMethodIndex基層に登録する — ただし定数リゾルバと.newディスパッチの両方が見られるクラスエントリーとしてキー付けする。これが新しい基層のケイパビリティ(capability)である。
  • WD2 — #innerの戻り値精度inner_arg_positionはADR-18のreturns_from_arg:機構を再利用する。リテラルな第2引数(Float、シェイプハッシュ、NilClass)はEnvironment#nominal_for_name(またはシェイプリテラル)を通じてバリアントの#inner戻り値へ解決する。ADR-16のWD13に従う下限: リゾルバチェーンが配線されるまではDynamic[T]
  • WD3 — sealed性sealed: trueは親 → バリアント集合を公開し、エンジンの制御フローナローイング(保留中のis_a?サーフェス)が後でバリアント集合を網羅的として扱えるようにする。ファクトをemitすることはスコープ内で、ナローイングのためにそれを消費することはスコープ外である。
  • WD4 — 需要ゲーティング。今日のティアDと同様に、まず値クラス + バリデーションを出荷し、バンドルされたコンシューマー(rigor-mangroveのEnumスライス(slice))がそれに対して構築されるときにプリパス + ディスパッチャー統合を配線する。
  • Mangroveを超えてADT / sealedバリアントのDSLのクラス全体(Dry::Structのネストしたattribute … do … endconst_setで生成するあらゆるマクロ)をアンブロックする。rigor-dry-structの保留メモが対処可能になる。
  • ティアがランディングするとrigor-mangroveは第2のスライス(Enum)を得て、すべてのMangroveサポートを1つのバンドルプラグインに保つ。
  • 真に新しい基層プリミティブ(クラスemit)を追加する。エンジンのサーフェス面積を増やすので、ADR-16の他の部分と同じ偽陽性の規律の背後でゲートされなければならない — 合成されたバリアントクラスは、動作しているプログラムの挙動を決して取り除いてはならず、Dynamic[Top]だったところに解決を加えるだけでなければならない。
  • 保留はv0.1.xの契約を誠実に保つ: これが出荷されるまで、rigor-plugin-authorスキルは、MangroveのEnumリクエストを、ウォーカーの回避策をでっち上げるのではなく「止まって尋ねる / ADRを開く」へ正しくルーティングする。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.