ADR-36 — Macro-substrate nested-class emission tier (Mangrove `Enum`)
ステータス: Accepted, 2026-05-30; Slice A implemented.
ADR-16のマクロ展開基層(substrate)を、クラスレベルのDSLブロックから(メソッドだけでなく)ネストしたサブクラスを生成する新しいティアで拡張するという決定を記録する。動機となったのはMangroveのEnum DSLである。Mangroveサポートのうち出荷可能で契約(contract)の範囲内にある部分(アンラップ呼び出しサイトでのキャリア(carrier)ジェネリックなインスタンス化)はplugins/rigor-mangroveとして別途ランディングした。このADRは、今日のプラグイン契約が表現できなかった部分をスコープする。
Slice A実装済み(2026-05-30): Plugin::Macro::NestedClassTemplate(マニフェストスロットnested_class_templates:)とSyntheticMethodScanner内のスキャナパスを追加した。これは、receiver_constraintをextendするクラス上の<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。
コンテキスト
Section titled “コンテキスト”MangroveのEnumは代数的データ型のDSLである:
class Shape extend Mangrove::Enum
variants do variant Circle, Float variant Rectangle, { width: Float, height: Float } variant Unit, NilClass endend各variant Const, Type宣言は、#inner : <Type>を担うネストしたサブクラスShape::Circle < Shapeを生成する(加えて固定のメソッド集合 — inner、as_super、serialize、==)ことを意図している。下流のコードは、それらのバリアントを構築しマッチする:
shape = Shape::Circle.new(1.0)case shapewhen Shape::Circle then shape.inner.floor # inner : Floatendなぜ現在の契約サーフェスが合わないか
Section titled “なぜ現在の契約サーフェスが合わないか”Mangroveはこれをconst_missing + 文字列化されたRubyヒアドキュメントのclass_evalで実装している — バリアントサブクラスはShape::Circleが初めて参照されたときに遅延的に定義される(調査メモ§「Enum DSL」)。生成されるサーフェスはソースから完全に回復可能である — variant Const, Typeの対はリテラル引数であり、emitされるメソッド集合は固定である — が、v0.1.xのどの基層ティアもそれをemitできない:
- ADR-16のティアC(
Macro::HeredocTemplate)はsymbol_arg_positionでリテラルSymbolを抽出し、呼び出し元クラス上のメソッドをemitする。Mangroveのvariantは定数(バリアントクラス名)を取り、emit先はShape上のメソッドではなく新しいネストクラスである。rigor-dry-structはすでにこの正確なギャップを文書化している: 「ネストブロック形式(attribute :details do ... endがAddress::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 … end、const_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.