プラグイン
プラグインが存在する理由はひとつ: 一部のメソッドの型が、どんなRBSシグでも表現できない方法でランタイムでの引数のシェイプ(shape)に依存するからです。この章は、それがプラグインに値するのはいつか — そして同じくらい多くの場合、値しないのはいつか — を判断する助けになります。
この章はプラグインの作成は教えません。それはexamples/にあります — 16個のチュートリアルウォークスルーで、それぞれが1つの拡張サーフェスにスポットを当てています — 一方、実際のフレームワーク向けのすぐにインストールできるgemはplugins/にあります。プラグインが必要かどうかを判断するには読み進めてください;作成したくなったらexamples/へ、既存のものをインストールするならplugins/へ進んでください。
この章の内容 プラグインを使うとき · プラグインを書くべきか? — まずこれを読む · プラグインが今日できること · マクロ / DSL展開基板 · 次に読むもの
プラグインを使うとき
Section titled “プラグインを使うとき”典型的なケースはドメイン固有の評価器です:
Lisp.eval([:+, 1, 2]) # ランタイムでIntegerLisp.eval([:<, 1, 2]) # ランタイムでboolLisp.eval([:if, true, "a", 0]) # ランタイムでString | Integer戻り値型は引数配列の先頭のリテラルシンボルに依存します。RBSはここでuntypedしか言えません; Rigorの推論にはどうしようもありません; RBS::Extendedディレクティブは引数のシェイプで変えられません。プラグインならできます。
プラグインのニッチに当てはまる他の形状:
- 単位DSL —
100.kilometers / 2.hoursはSpeedを生成しますが、Rubyのランタイムはユーザークラスを返すIntegerのメソッドとして見ます。 - ルートヘルパー —
users_pathはStringを返しますが、ヘルパーが存在するかどうかは解析器が読む必要があるYAMLファイルに依存します。 - ステートマシン —
transition_to(:foo)は、:fooがどこかで宣言されたstate_machine do ... endブロック内にある場合には有効ですが、そうでなければタイポです。 - カスタムバリデーター —
validate(:email, value)は解析時に名前付きパターンに一致しないリテラルを捕捉すべきです。
これらのそれぞれにexamples/に実例があります。examples/README.mdページは16の実例をアーキテクチャ軸(設定スキーマ、ファイルI/O、キャッシュプロデューサー、Scope#type_ofを通じたエンジン連携、クロスプラグインファクト(fact)、戻り型コントリビューションなど)で比較し、読む順序を推奨しています。
プラグインが今日できること
Section titled “プラグインが今日できること”まだここにいますか? ほとんどの読者はまずプラグインを書くべきか?へ飛ぶべきです — 答えはたいてい「いいえ、RBSと
RBS::Extendedで事足ります」です。下記のサーフェスは、本当に「はい」のときのためのものです。
v0.1.0+プラグイン契約(contract) — docs/internal-spec/plugin.mdに固定されており、同ディレクトリのいくつかのスライス(slice)仕様に展開されています — はプラグインに5つの主要サーフェス(surface)を与えます:
#diagnostics_for_file(path:, scope:, root:)— ファイルごとの出力フック。解析されたASTを辿り、Rigor::Analysis::Diagnostic行の配列を返します。ランナーは各行にsource_family: "plugin.<your-id>"をスタンプします。#flow_contribution_for(call_node:, scope:)— コールサイトごとの戻り型コントリビューションフック(v0.1.1 Track 2スライス7)。プラグインはコールサイトでの推論された戻り型を命名したRigor::FlowContributionバンドルを返します;解析器のディスパッチャーはコントリビューションをマージし、マージされた戻り型をRBS宣言済みかのように使います。Plugin::IoBoundary#read_file/#open_url— アクティブなTrustPolicyの下でサンドボックス化されたファイルおよび(v0.1.2以降)HTTPSの読み取り。プラグインがプロジェクトファイル(ルートテーブル、スキーマ、ロケールファイル)を読む、または安定したURLをフェッチする必要があるときに使います。Plugin::Base.producer+#cache_for— プラグイン側キャッシュプロデューサー。クロスランキャッシングが欲しいほど高コストなパース/ルックアップに使います。IoBoundaryが結果を構築している間に読んだすべてのファイルのダイジェスト(およびURLのコンテンツハッシュ)で自動的に無効化されます。Plugin::FactStore+#prepare(services)— クロスプラグインファクト公開サーフェス(v0.1.1 Track 2、ADR-9)。プラグインはprepareでファクトを公開します;下流のプラグインはservices.fact_storeを通じてそれらを消費するため、プロデューサー側の解析(例:config/routes.rb)をすべてのコンシューマーで再利用できます。
v0.1.2リリースは4つの実例(rigor-lisp-eval、rigor-pattern、rigor-units、rigor-activerecord)を「診断専用」から「flow_contribution_forを通じてナローイング(narrowing)された戻り型」に移行したため、プラグイン型の値へのチェーンされたコールがRBSレベルのuntypedエンベロープではなく解析器の通常のディスパッチで解決されます。各プラグインのREADMEを参照して、それぞれがどのサーフェスをデモしているか確認してください。
マクロ / DSL展開基板(ADR-16)
Section titled “マクロ / DSL展開基板(ADR-16)”上記の手書きウォーカー契約の上に、2つ目の作成パスが追加されました: マクロ展開基板(ADR-16)。メタプログラミングを多用するDSL — Railsスタイルのhas_one_attached、dry-structのattribute、Deviseのdevise :strategy、Sinatraのget '/foo' do ... end — に対して、基板はプラグイン作者がASTを手で歩く代わりにコール形状を宣言することを可能にします。プラグインの本体は単一のマニフェストエントリーになります;基板がリテラルシンボル抽出、名前補間、レジストリルックアップ、メソッドごとの合成を処理します。
4つのティア形状が認識されます。ライブラリごとのサーベイが、どのライブラリが各ティアに収まり、どのライブラリが基板のスコープ外に該当するかを特定します。
| ティア | 形状 | マニフェスト宣言 | 動作例 |
|---|---|---|---|
| A — ブロック-as-メソッド | DSL呼び出しのブロックがレシーバークラス上のインスタンスメソッドとして実行される(Sinatra::Base#generate_method) | block_as_methods: [Macro::BlockAsMethod.new(receiver_constraint:, verbs:)] | rigor-sinatra |
| B — トレイトインライニングレジストリ | クラスレベルの呼び出しがシンボルを列挙 → バンドルされたレジストリが各々をモジュールにマップ → 基板がモジュールのRBSメソッドを呼び出し元クラスに展開 | trait_registries: [Macro::TraitRegistry.new(receiver_constraint:, method_name:, modules_by_symbol:, always_included:)] | rigor-devise |
| C — heredocテンプレート | クラスレベルの呼び出しがリテラルシンボルをメソッド名テンプレートに補間;基板が合成リーダーを発行 | heredoc_templates: [Macro::HeredocTemplate.new(receiver_constraint:, method_name:, symbol_arg_position:, emit:)] | rigor-dry-struct |
| D — 外部ファイルインクルージョン | globにマッチするファイルが、宣言されたクラスとして型付けされたselfで実行される | external_files: [Macro::ExternalFile.new(glob:, receiver_type:, bound_ivars:)] | (v0.1.x時点では契約のみ — エンジン統合は需要駆動) |
上記の3つのTier A/B/Cプラグインは各々60〜110 LoCの純粋に宣言的なRuby — ウォーカーなし、diagnostics_for_fileなし、プラグイン側の状態なし。基板のプレパス + ディスパッチャー統合が作業を行います。
Concern再ターゲティング
Section titled “Concern再ターゲティング”ActiveSupport::Concern.included do ... endは遅延されたclass_eval: ブロック内のDSL呼び出しはconcernモジュール自身ではなく、includeした人に対して発火します。基板のスキャナはこの再ターゲティングを自動的に処理します。次のようなソースに対して:
module Auditable extend ActiveSupport::Concern included do attribute :audited_at, Types::Time endend
class Address < Dry::Struct include Auditable attribute :city, Types::StringendAddressはcity(直接)AND audited_at(Auditableから再ターゲティング)の両方を合成リーダーとして取得します。同じパターンがTier Bトレイト(Concern経由でincludeされるDeviseモジュール)でも動作します。
フロア / シーリング
Section titled “フロア / シーリング”ADR-16 § WD13に従い、フロアは合成メソッドが名前で発行されることであり、これによってクロスファイルディスパッチが解決されます(call.undefined-methodなし)。一般的なケースでは精密な戻り型も回復されます: Tier Bは由来モジュールが著作したRBSに再ディスパッチし(Deviseのvalid_password?はDynamic[T]ではなくboolに解決される)、Tier Cは素のクラス名の戻り値をそのNominalに解決します。依然Dynamic[T]に縮退するのは、パラメータ化された/ユーティリティ型形のTier Cの戻り値(Array[String]、Pick<T, K>)です;それらをADR-13リゾルバチェイン経由でルーティングすることがシーリングで、需要駆動です。基板はADR-5のロバストネスに従い、精度を捏造しません。
基板と手書きウォーカーの選択
Section titled “基板と手書きウォーカーの選択”| DSLが… | 基板を使う | 手書きウォーカーを使う |
|---|---|---|
クラスレベル呼び出し + リテラルシンボル引数 + フレームワークclass_eval'dヘレドック | ✓ Tier C | — |
クラスレベル呼び出し + リテラルシンボル引数 + レジストリ駆動のモジュールinclude | ✓ Tier B | — |
クラスレベル呼び出し + インスタンスメソッドとして実行されるdo…endブロック | ✓ Tier A | — |
宣言されたself下でinstance_eval'dされた外部Rubyファイル | ✓ Tier D(v0.1.x時点では契約のみ) | — |
戻り型が引数のシェイプに依存するドメインDSL | — | flow_contribution_for(rigor-lisp-eval) |
クロスファイル検証(宣言を収集してから使用を検証) | — | 2パスウォーカー(rigor-statesman) |
外部プロジェクトファイル(ルート、スキーマ、ロケール)のパース | — | IoBoundary + キャッシュプロデューサー(rigor-routes) |
スキーマグラフレコーダー(GraphQL-Rubyスタイル) | — | スキーマ解決パス(プラグインまだ未作成) |
基板と手書きウォーカー契約は共存します — プラグインはmanifestで宣言された基板エントリーをdiagnostics_for_fileウォーカーと混在させられます。skills/rigor-plugin-author/SKILL.md SKILLが決定フローを詳細にキャプチャします;docs/notes/20260515-macro-expansion-library-survey.mdのサーベイが、基板がどのRubyライブラリをカバーし、どのライブラリがスコープ外に該当するかを記録します。
プラグインを書くべきか?
Section titled “プラグインを書くべきか?”おそらくそうではありません — ほとんどのプロジェクトは、プラグインのニッチに達する前にRBSとRBS::Extendedから恩恵を受けます。プラグインに手を伸ばすのは以下の場合のみです:
- ドメインDSLの型付けが引数のシェイプ、ファイルの内容、またはクロスメソッド宣言に依存している。
- アプリケーションと共にプラグインgemを保守する意欲がある。
- チームがプラグインのソースを読める — それは誰も無視できるブラックボックスではありません。
これらが当てはまるなら、examples/README.mdが出発点です。rigor-deprecationsの例は80行未満で、「最初のプラグインを書きたい」のための推奨テンプレートです。
次に読むもの
Section titled “次に読むもの”ハンドブックの終わりに到達しました。ここからは:
- 通読しなおすことはほとんど有用ではありません — ほとんどの読者は疑問が生じたときに特定の章に戻ります。
- ハンドブック索引には
docs/type-specification/、docs/internal-spec/、docs/adr/のより深い素材への相互参照があります。 CHANGELOG.mdはいつ何が出荷されたかのリリースごとの真実です。
静的Rubyを信じる小さな、成長中のコミュニティへようこそ。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.