Macro / DSL Expansion Substrate
マクロサーフェス(macro substrate、マクロ基盤)は、メタプログラミング
ライブラリが利用者に公開する呼び出し形状をRigorに教えるために、プラグイン
作者が宣言するプラグインマニフェストの値オブジェクト群である。エンジンが
ソースを読むだけでは見えないdefine_method / class_eval / const_missing
パターンを指す。これはADR-16(4ティア)で
導入され、ADR-18(呼び出し
箇所ごとの戻り値型)と
ADR-36(ネストクラスの
発行)で拡張された。
本ドキュメントはプラグイン作者向けの値オブジェクト形状を規定する。
すなわち、フィールド、その型、検証、そして各エントリーが満たす同一性/
不変性の契約(contract)である。これらの形状は、プラグインgemが基づいて
記述される永続的な契約である。エンジン側での消費 ── ディスパッチャーの
合成メソッドティアとEnvironment#synthetic_method_index ──
はinference-engine.mdが規範的に定める
(ディスパッチャーのティア順序+Environmentのクエリサーフェス)。
floor/ceiling配信ポリシーとティアごとの根拠はADRにある。
5つのクラスはすべてRigor::Plugin::Macro名前空間
(lib/rigor/plugin/macro/)の下にあり、plugin.md
で文書化された対応するManifestスロットを通じて宣言される。
block_as_methods:、heredoc_templates:、trait_registries:、
external_files:、nested_class_templates:。
共通の値オブジェクト契約
Section titled “共通の値オブジェクト契約”すべてのマクロ値オブジェクトは、同じ同一性および不変性の規則をMUST満たす (残りのプラグイン契約キャリア(carrier)と一貫している)。
- 構築時にフリーズ。すべてのフィールドは
#initialize内でdup-freeze され、インスタンス自体もフリーズされる。構築後Ractor.shareable?は trueをMUST返す(ADR-15 Phase 1)。これにより、実体化されたプラグインは fork/Ractor境界を越えてサーフェス宣言を運ぶ。 - 構築時に検証。各フィールドは
#initialize内でチェックされる。 不正な宣言は、スキャン時に黙って失敗するのではなく、マニフェスト ビルド時(ロード時)にArgumentErrorをMUST送出する。Manifestスロットは、すべてのエントリーが期待されるクラスのインスタンスであることを 再検証する。 - 値による同一性。
#==/#eql?/#hashはフィールド値によって 比較する(ほとんどは正準的な#to_hを介する)ため、構造的に等しい2つの 宣言は交換可能である。 #to_hはキャッシュキーへとラウンドトリップする。すべてのクラスは 文字列キーの#to_hを公開する。マニフェストはそれをManifest#to_hに 畳み込み、これはキャッシュキーとして安定している。プラグイン作者は、 エンジン統合が先送りされている場合(後述の_実装状況_を参照)でも、 今日どのティアでもMAY宣言できる。宣言はラウンドトリップし、対応するManifestリーダーで公開され、エンジンスライス(slice)が着地したときに 前方互換となる。receiver_constraintのマッチング。ティアがreceiver_constraintを 運ぶ場合(ExternalFileを除くすべて)、呼び出しの字句的レシーバー クラスがその完全修飾名と等しいか、それを継承しているときにエントリーが 発火し、Environment#class_orderingを通じてマッチされる。
ティアA ── BlockAsMethod(block_as_methods:)
Section titled “ティアA ── BlockAsMethod(block_as_methods:)”「verbsのいずれかのクラスレベルDSL呼び出しに渡されたブロックは、
receiver_constraintのサブクラスツリー上のインスタンスメソッドとして
実行され、selfはそれに応じて型付けされる。」正準的なターゲット:
Sinatraのget '/path' { ... }(ブロックは文字どおりルートメソッド本体に
なる)。
| フィールド | 型 | 注記 |
|---|---|---|
receiver_constraint | 空でないString | 呼び出しの字句的レシーバーがそれであるか、それを継承していなければならないFQクラス名。 |
verbs | 空でないArray<Symbol> | ブロックがインスタンスメソッドとして実行されるDSLメソッド名(Symbol/空でないStringから強制変換)。 |
self_type | Symbol | ブロック内のselfバインディングの種類。デフォルトかつ現在唯一の有効な値::receiver_instance。:receiver_singleton / :dsl_recorderは予約名であり、まだ受理されない。 |
ティアC ── HeredocTemplate(heredoc_templates:)
Section titled “ティアC ── HeredocTemplate(heredoc_templates:)”「クラスレベル呼び出し<receiver_constraint>.<method_name>(name_arg, …)は、
呼び出し元クラスに合成メソッドを発行し、その名前はsymbol_arg_positionの
ソースで可視なリテラル引数を補間する。」正準的なターゲット:dry-structの
attribute :name, TとActiveStorageのhas_one_attached :avatar。
| フィールド | 型 | 注記 |
|---|---|---|
receiver_constraint | 空でないString | FQクラス名(等しいか継承)。 |
method_name | Symbol | DSLメソッド(Symbol/空でないStringから)。 |
symbol_arg_position | Integer >= 0 | デフォルト0。各emit行に補間されるnameとなる、リテラルSymbol値を持つ引数のインデックス。 |
emit | Array<Emit> | 呼び出し元クラスに合成するインスタンスメソッド(Hashから強制変換)。 |
class_level_emit | Array<Emit> | 同じ形状。合成されるメソッドはシングルトン(クラスレベル)。 |
NAME_PLACEHOLDERは、emit行のname:テンプレートが補間のために運ぶ
リテラル"#{name}"トークンである。
HeredocTemplate::Emit
Section titled “HeredocTemplate::Emit”emitテーブルの1行。
| フィールド | 型 | 注記 |
|---|---|---|
name | 空でないString | 合成メソッドの名前テンプレート。"#{name}"プレースホルダーはsymbol_arg_positionの呼び出し箇所リテラルシンボルで補間される。 |
returns | 空でないStringまたはnil | 宣言された戻り値型名。Environment#nominal_for_nameを介して解決される。returnsとreturns_from_argの両方がnilのとき、合成メソッドの戻り値はADR-16 WD13のfloorに従ってDynamic[Top]にフォールバックする。 |
returns_from_arg | ReturnsFromArgまたはnil | 呼び出し箇所ごとの戻り値型(ADR-18)。Hashから強制変換される。 |
HeredocTemplate::ReturnsFromArg(ADR-18)
Section titled “HeredocTemplate::ReturnsFromArg(ADR-18)”HeredocTemplateの下のEmitの兄弟クラス(Emitの中にネストされて
いない)で、Emit#returns_from_argから参照される。合成メソッドの戻り値型が
呼び出し箇所の引数のソース表現から来ることを宣言し、それはプラグイン
横断のファクト(fact)チャネル(ADR-9の
FactStore)で探索される。記述形状:
returns_from_arg: { position: 1, lookup_via: { plugin_id: "dry-types", fact: :dry_type_aliases } }| フィールド | 型 | 注記 |
|---|---|---|
position | Integer >= 0 | ソース表現が探索キーとなる呼び出し箇所の引数インデックス。 |
plugin_id | 空でないString | 生成側のプラグイン(lookup_via:から)。 |
fact | Symbol | そのプラグインのFactStoreバケットから読むファクト名(lookup_via:から)。 |
.coerce(value)はHash(lookup_via:のHashを要求する)、ReturnsFromArg、
またはnilを受理し、それ以外の形状ではすべて送出する。
ティアB ── TraitRegistry(trait_registries:)
Section titled “ティアB ── TraitRegistry(trait_registries:)”「クラスレベル呼び出し<receiver_constraint>.<method_name>(:trait_a, :trait_b, …)は、modules_by_symbol[:trait_a] + [:trait_b]で名指された
モジュール(および任意のalways_includedモジュール)を、呼び出し元
クラスに実質的にincludeする。」正準的なターゲット:Deviseのdevise :database_authenticatable, :recoverable。
| フィールド | 型 | 注記 |
|---|---|---|
receiver_constraint | 空でないString | FQクラス名(等しいか継承)。 |
method_name | Symbol | DSLメソッド(例::devise)。 |
symbol_arg_position | :restまたはInteger >= 0 | :rest(デフォルトであり、スキャナが尊重する唯一の形式)はすべての位置Symbol引数をトレイトとして扱う。Integerインデックスは将来の単一トレイト形状のために予約されている。 |
modules_by_symbol | Hash<Symbol, String> | 認識された各トレイトシンボルをFQモジュール名にマッピングする。テーブルに存在しないシンボルは素通りする(スキャナはmacro.tier_b.unknown-traitの:infoマーカーを発行する)。 |
always_included | Array<String> | シンボルが1つもマッチしない場合でも、マッチするすべての呼び出し箇所で追加されるFQモジュール名。 |
#module_for(symbol)はトレイトシンボルに対するFQモジュール名を返し、
不明な場合はnilを返す。ティアBはティアCのDynamic[T]のfloorの対象では
ない。合成されるメソッドは、includeされたモジュールが記述したRBS戻り値型を
再生する(ADR-5のロバストネス ── サーフェスは与えられていない精度を捏造
しない)。
ティアD ── ExternalFile(external_files:)
Section titled “ティアD ── ExternalFile(external_files:)”「globにマッチするファイルは、その本体が、selfがreceiver_typeの
インスタンスである(かつ@ivarファクトがbound_ivarsから来る)呼び出し
箇所に貼り付けられたかのように解析される。」動機となるケース:Redmineの
WebhookPayload#instance_eval(File.read(path), …)、tDiaryのプラグイン
ローダー。
| フィールド | 型 | 注記 |
|---|---|---|
glob | 空でないString | ファイルパターン。プロジェクトルート(.rigor.ymlを保持するディレクトリ)からの相対として解釈される。 |
receiver_type | 空でないString | ロードされたファイル内でselfがバインドするクラス名。 |
bound_ivars | Hash<String, String> | 各キーは@でMUST始まる。各値は空でない型名Stringである。ファイルエントリスコープでivarファクトとして事前バインドされる。 |
宣言のみ(エンジン統合は先送り)。ティアDは値クラス+マニフェスト スロットのみを提供する。マッチしたファイルを解析セットに追加し、ファイル エントリーの
self_typeをナローイングし、bound_ivarsを事前バインドする エンジン統合は、実証された需要を条件として、ADR-16のスライス5bに キューイングされている。宣言されたエントリーはラウンドトリップし、Manifest#external_filesで公開されるが、サーフェスはまだそれに作用 しない。
ネストクラスティア ── NestedClassTemplate(nested_class_templates:、ADR-36)
Section titled “ネストクラスティア ── NestedClassTemplate(nested_class_templates:、ADR-36)”ティアCがメソッドを合成するのに対し、このティアはenum形状のブロックDSLで
宣言されたネストされたサブクラスを合成する。動機となる形状:Mangroveの
variants do variant Circle, Float end。ここで各variant <Const>, <Type>
行は、#inner : <Type>を運ぶネストされたサブクラスShape::Circle < Shapeを
鋳造する。
| フィールド | 型 | 注記 |
|---|---|---|
receiver_constraint | 空でないString | ブロックが認識されるために、囲んでいるクラスがextendしなければならないFQモジュール名(例:"Mangrove::Enum")。 |
block_method | Symbol | 囲んでいるDSLブロック。デフォルト:variants。 |
variant_method | Symbol | ブロック内の各宣言呼び出し。デフォルト:variant。 |
name_arg_position | Integer >= 0 | デフォルト0。ネストされたサブクラスを名指すリテラル定数を持つ引数のインデックス。 |
inner_arg_position | Integer >= 0 | デフォルト1。型式が#innerリーダーの戻り値型となる引数のインデックス。定数型引数は解決される。定数でないinner形状はDynamic[Top]に降格する。 |
inner_reader | Symbol | 各バリアントサブクラスに合成されるペイロードリーダー。デフォルト:inner。 |
sealed-親ファクト+is_a?バリアント横断の網羅的ナローイング
(ADR-36 WD3)は、先送りされたceilingである。
| ティア | クラス | マニフェストスロット | エンジン状況 |
|---|---|---|---|
| A | BlockAsMethod | block_as_methods: | 稼働中(実装済みコンシューマー:rigor-sinatra)。 |
| B | TraitRegistry | trait_registries: | 稼働中(実装済みコンシューマー:rigor-devise)。 |
| C | HeredocTemplate(+Emit / ReturnsFromArg) | heredoc_templates: | 稼働中(実装済みコンシューマー:rigor-dry-struct / rigor-dry-types)。returns_from_argの呼び出し箇所ごとの探索はADR-18のレイヤー。 |
| D | ExternalFile | external_files: | 宣言のみ ── エンジン統合はADR-16のスライス5bに先送り。 |
| ネストクラス | NestedClassTemplate | nested_class_templates: | 稼働中、スライスA(実装済みコンシューマー:rigor-mangrove)。sealed-親の網羅性は先送り。 |
ADR-16 WD13に従い、サーフェスが生成する
出力はfloor(「サーフェスの影響を受けるコードはクリーンにパースされ、
識別子が解決される」)で提供される。精密な戻り値型の発行はceilingであり、
ティアごとにレイヤー化される(ティアCのreturns:文字列は
Environment#nominal_for_nameを介して。より豊かな形式にはADR-13の
TypeNodeResolverチェーンを介して)。
ドリフトピン状況
Section titled “ドリフトピン状況”public-APIドリフト仕様
(spec/rigor/public_api_drift_spec.rb)は、
BlockAsMethod、HeredocTemplate、HeredocTemplate::Emit、
HeredocTemplate::ReturnsFromArg、TraitRegistry、ExternalFile、
NestedClassTemplateのインスタンスメソッド集合をピン留めする ──
publicマニフェストサーフェス上の出荷済みの値オブジェクトはすべて、いまや
同じ偶発的変更ガードを運ぶ。かつてピン留めされていなかった2つの
オブジェクト(ADR-36のNestedClassTemplateとADR-18の
HeredocTemplate::ReturnsFromArg)は、
PLUGIN_MACRO_NESTED_CLASS_TEMPLATE_INSTANCEおよび
PLUGIN_MACRO_HEREDOC_TEMPLATE_RETURNS_FROM_ARG_INSTANCEスナップショット
定数を介してピン留めされた。これらのオブジェクトはまだsig/rigor/*.rbs
シグネチャを運ばないため、RBSのsig-driftの対ではなく、ランタイムの
インスタンスメソッドスナップショットのみによってガードされている。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.