推論エンジン
このドキュメントは、Rigor型推論エンジンが満たさなければならない公開契約(contract)を仕様化します。すなわちRigor::Scope#type_of(node)クエリ、不変スコープ規律、フェイルソフトなDynamic[Top]ポリシー、およびそれらを取り巻く環境ロード境界です。これはdocs/type-specification/の型言語セマンティクスとinternal-type-api.mdの型オブジェクト公開APIに対する、エンジン側の対応物です。
スライス(slice)単位の成長計画とADR-3の未決事項に対する暫定回答の根拠はdocs/adr/4-type-inference-engine.mdにあります。そのADRと本ドキュメントが観測可能なRubyの挙動について食い違うとき、本ドキュメントが束縛します。
本ドキュメントは以下を束縛します。
Rigor::Scope#type_of(node)クエリの形状と安定性。- そのクエリを取り巻く不変スコープ規律。
- 型付け器がまだ認識しないASTノードに対するフェイルソフトポリシー。
- 環境ロードの境界。すなわちどのサーフェス(surface)がMUST利用可能で、どのサーフェスがスライス間で変わってもMAY良いか。
本ドキュメントは以下を束縛しません。
Rigor::Scopeが用いる内部データ構造(公開サーフェスが保たれ、不変性が観測可能である限り)。Rigor::Inference::ExpressionTyperの内部で用いるビジターまたはパターンマッチ戦略。- 任意の特定スライスで認識されるPrismノードの正確なカタログ。そのカタログは参考情報であり、
docs/adr/4-type-inference-engine.mdで追跡されます。
Scope#type_of(node)契約
Section titled “Scope#type_of(node)契約”Rigor::Scope#type_of(node)はMUST純粋なクエリです。レシーバースコープやそこから到達可能な任意のオブジェクトをMUST NOT変更し、解析器の他のどこでも永続的な状態変更をMUST NOT引き起こしません。同じ(scope, node)ペアは、単一の解析器実行内の呼び出しを通じて構造的に等しいRigor::Typeの結果をMUST生成します。
クエリはinternal-type-api.mdに従ってRigor::TypeをMUST返します。nilをMUST NOT返し、サポートされないノードでMUST NOTraiseし、戻り値にPrismオブジェクトをMUST NOT露出させません。
レシーバーはMUSTRigor::Scopeのインスタンスです。実装は生のHashや束縛のArrayをMUST NOT受け付けません。束縛コンテナはRigor::Scopeの内部実装です。
node引数はMUSTPrism::NodeまたはRigor::AST::Node(後述の仮想ノードファミリーからの合成ノード)のいずれかです。実装は、上流のPrismで追加されたときに追加のPrismノードファミリーをMAY受け付け、またエンジンを通じて登録されたときに追加のRigor::AST::Node部分型をMAY受け付けますが、いずれのファミリー内でも認識されない具象クラスは後述のフェイルソフトポリシーの下でMUST扱い、raiseしてはなりません。
不変スコープ規律
Section titled “不変スコープ規律”Rigor::ScopeのインスタンスはMUST不変です。構築の最後にMUSTfreezeされます。任意の公開または内部メソッドを通じた変更は契約違反であり、内部コンテナを公開するアクセサ経由のものも含みます。
状態変化はMUST明示的な遷移メソッドから返される新しいスコープとして表現されます。最小セットは以下のとおりです。
Rigor::Scope.empty(environment:)— ローカル束縛なし・空のRigor::Analysis::FactStoreを持つスコープを構築し、Rigor::Environmentに紐づけます。Rigor::Scope#with_local(name, type)—nameがtypeに束縛されている点を除いてレシーバーと同じ新しいスコープを返します。ローカルを再束縛するとMUSTそのローカルを対象とするファクトを無効化します。Rigor::Scope#local(name)— 束縛されたRigor::Typeを返すか、nameが束縛されていなければnilを返します。Rigor::Scope#fact_store— スナップショットに紐づく不変なRigor::Analysis::FactStoreを返します。Rigor::Scope#local_facts(name, bucket: nil)/Rigor::Scope#facts_for(target:, bucket: nil)— バケットストレージを直接公開せずに、スコープのRigor::Analysis::FactStoreからファクト(fact)を返します。Rigor::Scope#with_fact(fact)—factがファクトストアに追加された新しいスコープを返します。Rigor::Scope#join(other)— 制御フロー合流点で新しいスコープを返します。実装は、2つのスコープが同じEnvironmentを共有することをMUST要求します。結合されたスコープは、両方のレシーバーが束縛するすべての名前にMUST束縛されます。そのような各名前について、結合された型はMUSTType::Combinator.union(self.local(name), other.local(name))です。一方のレシーバーにのみ束縛されている名前は、結合されたスコープからMUST落とされます。半束縛の名前のnil注入は文レベル評価器(docs/adr/4-type-inference-engine.mdのSlice 3を参照)の責任であり、#joinの責任ではありません。結合されたファクトストアは、両方の入力エッジに存在するファクトのみをMUST保持します。
Rigor::Scopeは、有用な箇所で基底データを構造的にMUST共有します。親を共有し1つの束縛で異なる2つのスコープは、他のすべての束縛のストレージをMAY共有します。これは実装の詳細であり、契約の一部ではありません。
Rigor::Scope#environmentは、スコープを構築したのと同じRigor::EnvironmentインスタンスをMUST返します。環境は、スコープの観点からはクエリの期間中は不変として扱われます。
FactStore(Slice 6 phase 2 sub-phase 2)
Section titled “FactStore(Slice 6 phase 2 sub-phase 2)”Rigor::Analysis::FactStoreは各Scopeスナップショットが運ぶ不変なファクト束です。最初の実装は意図的に小さいですが、control-flow-analysis.mdからの長期的なバケット形状を確立します。
Rigor::Analysis::FactStore.emptyは空のフローズンストアをMUST返します。FactStore::Target.local(name)はローカル束縛ターゲットを識別します。FactStore::FactはMUSTbucket・target・predicate・payload・polarity・stabilityを運びます。認識されるバケットはlocal_binding・captured_local・object_content・global_storage・dynamic_origin・relationalです。FactStore#with_fact(fact)は新しいストアをMUST返し、構造的に等しいファクトを重複排除します。FactStore#invalidate_target(target, buckets: nil)はtargetに言及するファクトが除去された新しいストアをMUST返します。buckets:が指定されたとき、それらのバケット内のファクトのみが除去されます。FactStore#join(other)は両方のストアに存在する構造的に等しいファクトのみをMUST保持します。
ファクトストアのバケットは内部的な最適化境界です。Scopeはファクトクエリとファクト追加遷移を公開してかまいませんが、呼び出し元はバケットストレージをin-placeでMUST NOT変更しません。
フェイルソフトポリシー
Section titled “フェイルソフトポリシー”型付け器がまだ認識しないノード — エンジンがまだ配線していないクラスのPrismノードか、未知の種別のRigor::AST::Nodeのいずれか — に出会ったとき、Scope#type_of(node)は「型なし、未チェック」の標準的なDynamic[Top]表現であるRigor::Type::Combinator.dynamic(Rigor::Type::Combinator.top)をMUST返します。
フェイルソフト経路はMUST以下を満たします。
- MUST NOTraiseします。呼び出し元はPrismが生成する任意の式ノード、および
Rigor::AST::Nodeを含む任意の合成ノードについてScope#type_ofにMAY依存できます。 value-lattice.mdの動的起源代数をMUST保ちます。返された型に対する下流クエリは、他のあらゆるDynamic[T]が観測するのと同じ漸進的型付け規則をMUST観測します。- 後述のフォールバックトレーサー契約を通じて、計装にMUST観測可能です。
スライスがあるノード種別のサポートを導入するとき、その種別のフェイルソフト経路は同じスライス内でMUST除去されます。型付け器は、誤って型付けされたノードをマスクするフォールバックをMUST NOT残しません。
フォールバックトレーサー
Section titled “フォールバックトレーサー”Rigor::Scope#type_ofはオプショナルなtracer:キーワード引数をMUST受け付けます。tracerがnil(デフォルト)のとき、エンジンはトレーサーが取り付けられていないかのようにMUST振る舞います。すなわちMUSTイベントは記録されず、フェイルソフト経路で戻り値を生成するために必要な範囲を超える割り当てがMUST行われません。
tracerが非nilのとき、フェイルソフトフォールバック(PrismとSyntheticの両方)はすべて、単一のメソッド呼び出しを通じてトレーサーにMUST記録されます。
tracer.record_fallback(event)eventはMUSTRigor::Inference::Fallback値オブジェクトで、構造的に等しい以下のフィールドを持ちます。
node_class— フォールバックを引き起こしたノードのRubyのClass(例:Prism::CallNode・Rigor::AST::SomeFutureNode)。location— 実際のPrismノードに対するPrismソース位置オブジェクト、または位置を露出しない合成ノードに対するnil。family— 実際のPrismノードに対するシンボル:prism、Rigor::AST::Nodeを含むノードに対する:virtual。inner_type— 呼び出し元に返されたRigor::Type。今日はDynamic[Top]です。後のスライスはフォールバックを観測可能に保ちながら内部型をMAYリッチにします。
Rigor::Inference::FallbackTracerが公開するトレーサープロトコルはMUST以下を満たします。
record_fallback(event)は任意のRigor::Inference::FallbackをMUST受け付け、他の引数を拒否します。eventsはMUST記録済みイベントのフローズンで順序付けされたスナップショットを返します。empty?とsizeはMUST現在の記録済みイベント数を報告します。eachはMUST挿入順に記録済みイベントを反復します。トレーサーはMUSTinclude Enumerableします。
トレーサーはScope#type_ofから観測可能な唯一の可変状態です。type_ofの戻り値をMUST NOT変更し、Rigor::Scopeのアクセサを通じてMUST NOT公開されません。実装は追加のrecord_*メソッドをMAY追加できます(例えばSlice 2ディスパッチャーがティアを得たときのよりリッチなrecord_dispatch_missや、Slice 6でのrecord_budget_cutoffなど)。これにより複数のイベントファミリーが1つのトレーサーを共有します。新しいメソッドはMUST上記の不変イベント値オブジェクトパターンに従います。
エンジンは、Prismノードに加えてMUST合成ASTファミリーを受け付けます。合成ノードは、ドキュメンテーション専用のマーカーモジュールRigor::AST::Nodeをincludeし、エンジンがRigor::Typeへ翻訳するために必要なノード固有データを公開するRubyオブジェクトです。これにより、実際のPrism式を構築せずに「型Tの値がここに現れたら解析器は何を推論するか?」をScope#type_ofに問えるようになります。
合成ノードはMUST以下を満たします。
- MUST不変です。
Rigor::AST::Nodeは構築時にMUSTfreezeされます。 - MUST構造的等価性をサポートします。構造的に等価なデータを持つ2つの合成ノードは、
==とeql?の下でMUST等しく比較され、同じhashをMUST共有します。 - 合成ノードが内部AST位置を持つとき、実際のPrismの子とMUST合成可能です。エンジンはすべての推移的な子が合成であることをMUST NOT要求しません。
- 解析器の状態やファクトストアのエントリーをMUST NOT運びません。そのような状態は
Rigor::Scopeまたはエンジンの環境にあり、ノード上にはありません。
Scope#type_of(virtual_node)はPrismノードと同じフェイルソフト契約を通じてディスパッチされます。Rigor::AST::Node内の認識されない具象クラスはraiseするのではなくMUSTDynamic[Top]を返します。
Rigor::AST::TypeNode
Section titled “Rigor::AST::TypeNode”本仕様が束縛する最小の合成ノードファミリーはRigor::AST::TypeNodeです。MUST存在し、MUSTRigor::AST::Nodeをincludeし、MUST単一のRigor::Typeから構築可能であり、MUST以下を満たします。
Rigor::Scope#type_of(Rigor::AST::TypeNode.new(t))は、任意の非nilなtについて、tと構造的に等しく比較されるRigor::TypeをMUST返します。- エンジンは内部型をMUST NOT変更・正規化・注釈付け・ラップしません。
TypeNodeを通じたラウンドトリップは観測可能に恒等です。 TypeNodeはScope#type_ofの副作用として、Dynamic[T]・リファインメント(refinement、篩型とも)・任意の他のキャリア(carrier)にMUST NOTラップされません。
追加の合成ノード種別(呼び出し式・コンテナリテラル・ナローイング(narrowing)ラッパー)は後のスライスで追加され、昇格されるまで規範的ではありません。新しい種別はMUST上記の不変性・構造的等価性・合成可能性の規則に従います。
メソッドディスパッチ境界
Section titled “メソッドディスパッチ境界”メソッドディスパッチ(レシーバー型と引数型から呼び出し式の結果型を決定する規則)はMUST NOTRigor::Typeのインスタンスに存在しません。型クラスはinternal-type-api.mdに従って薄い値オブジェクトのままです。すなわち、構造的データを保持しケイパビリティ(capability)の問いに答えますが、メソッド要約テーブルや演算子ハンドラは運びません。
Slice 2はRigor::Inference::MethodDispatcherを別のエンジンサーフェスとして導入します。元はSlice 3で計画されていましたが、rigor type-scanによるドッグフードシグナルがPrism::CallNodeとPrism::ArgumentsNodeが認識されない式の最大の単一ソースであることを示したため前倒しされました。Slice 2ディスパッチャーは定数畳み込みティアのみを出荷します。Slice 4はその背後にRBSバックのルックアップを重ねます(docs/adr/4-type-inference-engine.mdを参照)。
ディスパッチャーの公開シグネチャは以下のとおりです。
Rigor::Inference::MethodDispatcher.dispatch( receiver_type:, # Rigor::Type or nil (implicit self; unsupported in Slice 2) method_name:, # Symbol arg_types:, # Array<Rigor::Type> block_type: nil, # Rigor::Type or nil; the inferred return type of an # accompanying `do ... end` / `{ ... }` block. # Slice 6 phase C sub-phase 2: when non-nil, the # selector prefers a block-bearing overload and the # method-level type parameter that the block's # return type references is bound to `block_type` # so a return like `Array[U]` resolves to # `Array[block_type]`. environment: nil, # Rigor::Environment; required for RBS-backed dispatch scope: nil # Rigor::Scope or nil; when present, enables the # ADR-43 RBS-complete-ancestor resolution described # in the RBS-backed tier below. Defaults to nil, in # which case that resolution is inert and every other # tier behaves identically.) #=> Rigor::Type, or nil when no rule matchesnilの戻り値は意図的な「規則なし」のシグナルです。呼び出し元はフェイルソフトフォールバックをMUST所有します(ExpressionTyperはFallbackTracerイベントを記録してDynamic[Top]を返します)。ディスパッチャー自体はトレーサーにMUST NOT触れず、認識されない入力でMUST NOTraiseします。
ディスパッチャーはMUST以下の順序でティアを参照します: 精密ティア(定数畳み込み、形状認識、ファイル畳み込み、カーネル畳み込み、ブロック畳み込み)を最初に、次にプラグイン戻り型コントリビューションティア(v0.1.1 Track 2スライス7、ADR-2 §「プラグインコントリビューションマージ」に従う)、次にHKT組み込み戻り値ティア(ADR-20)とRigorバンドルの静的リファインメントティア、次にRBSバックのディスパッチティア、次にADR-16合成メソッドティア(基板Tier B / Tier Cのemitテーブル — ユーザー著作のRBSはなおその上で勝つ)、次にADR-17プロジェクトパッチメソッドティア(pre_eval:宣言された再オープンクラスのメソッドのためにEnvironment#project_patched_methodsを参照し、Dynamic[Top]を返して呼び出しがcall.undefined-methodなしにクロスファイルで解決されるようにする)、次に依存関係ソース推論ティア(v0.1.3 / ADR-10スライス2b-ii — オプトインGemの(class_name, method_name)カタログエントリーのためにEnvironment#dependency_source_indexを参照し、ヒット時にDynamic[Top]を返してコールサイトが動的由来マーカーを持つようにする)、次に発見済みメソッドティア(ADR-24に従うクロスファイルのユーザー定義def解決、上記のScope#user_def_for / 祖先サーフェス経由)、最後にRigorが名前で知っているがRBSでは知らないレシーバーに対してObject / Classに対して再試行するユーザークラス祖先フォールバック。非nilのRigor::Typeを返す最初のティアが勝ちます。ヒット時に後続のティアはMUST NOT参照されません。ディスパッチャーは、Prismの子ノードまたは(上記の仮想ノード契約を介した)合成Rigor::AST::Node引数のいずれかを運べる統一された呼び出し形状として入力をMUST取り、これによって合成された式と実際の式が単一のディスパッチ経路を共有します。
Dispatch tier contract
Section titled “Dispatch tier contract”上記の各ティアは構造的インターフェースです ── ダックタイプされた継ぎ目であり、RBS/Goの意味でのインターフェースであって、ADR-28のプロトコル契約ではありません。これはsig/rigor/inference.rbsにおいてinterface _DispatchTierとして成文化されています。ティアは単一のエントリーポイントをMUST公開します。
try_dispatch(context) #=> Rigor::Type, or nil to defer to the next tiercontextは不変のRigor::Inference::MethodDispatcher::CallContext値オブジェクト(Data)であり、ティアが一つのコールサイトを畳み込むのに必要なすべてを運びます。すなわち呼び出しの四つ組(receiver・method_name・args)に加え、省略可能なblock_type・environment・call_node・scope・self_type_override・public_onlyフィールドです。dispatchはコンテキストを一度だけ構築し、それを ── 変更せずに ── すべてのティアへMUSTスレッドします。異なるレシーバーや派生フラグ(ユーザークラスフォールバックのpublic_only再試行、由来モジュールの再ディスパッチ)を必要とするティアは、与えられたものを変更するのではなく、コピー(CallContext#with)によってか新しいものを構築することによって、新鮮なコンテキストをMUST取得します(値オブジェクトは凍結されています)。純粋なティア(定数畳み込み、形状認識、シングルトン畳み込み)はreceiver / method_name / argsのみをMUST読み、残りを無視します。上記の「最初の非nilが勝つ」ルールがこれらのティアの順序付けされた参照を統べます。
この契約は宣言的です。すなわち基底クラスやimplements句(Rubyには存在しません)によって強制されるものではなく、バンドルされたティアはSteepの寛容モードの下で未型付けのままです。実装は_DispatchTierに適合させて正しい優先度に挿入することでティアをMAY追加できます。エントリーポイントのキーワードサーフェスを再び広げてはMUST NOTなりません(値オブジェクトはまさにティアごとのシグネチャを一様に保つために存在します)。
RBSバックのティアは、レシーバー型をkindが:instanceまたは:singletonである(class_name, kind)ペアにMUST解決します。
Type::Constant[v]は(v.class.name, :instance)に解決されます。Type::Nominal[name]は(name, :instance)に解決されます。Type::Singleton[name](Slice 4 phase 2b)は(name, :singleton)に解決されます。ディスパッチャーはこの種別についてMUSTinstance_methodではなくRbsLoader#singleton_methodを参照し、これによりFoo.barはFooのクラスメソッドを正しくルックアップします。Type::Dynamic[T]は同じ規則を用いてTの静的ファセットへ再帰します。Type::TopとType::Botはディスクリプタを生成しません。ディスパッチャーはMUSTnilを返します。
Unionレシーバーは各メンバーをMUST個別にディスパッチします。すなわちすべてのメンバーが解決すると、メンバーごとの戻り値型がunionされそのunionが返されます。任意のメンバーがnilを返すと、全体のディスパッチはMUSTnilを返します。インスタンスとシングルトンのメンバーを単一のunion内で混ぜることはMUST NOT特別扱いされません。各メンバーはそれぞれのディスクリプタに対してディスパッチされます。
RBS完全祖先解決(ADR-43)。解決された(class_name, kind)ディスクリプタのクラスがRBS既知でないとき(ScopeIndexerが発見したRubyソース由来の部分型で、RBS環境のクラス宣言に存在しない場合)、直接のメソッドルックアップは外れ、そのクラスがRBSにとって未知であるため祖先ウォークも走らず、呼び出しはDynamic[Top]へ劣化することになります。これは偽陽性に安全なデフォルトです。すなわちRBSのみのクラスを部分型化したRubyクラス(class MyController < ActionController::Base)は、部分的なgemのRBSが省略しているメソッドにも日常的に応答するため、その継承された呼び出しをそのRBSに対して解決してcall.undefined-methodを発火させると、動作しているコードを脅かすことになります。したがってディスパッチャーは一般的なケースについてはそのフォールバックをMUST維持します。限定された例外として、(a)scope:がdispatchへスレッドされ、(b)class_nameがRBS既知でなく、(c)class_nameの発見済み上位クラス連鎖(Scope#superclass_of、ADR-24)がエンジンの凍結されたALLOWED_RBS_COMPLETE_ANCESTORS許可リスト上のクラスへ到達するとき、ディスパッチャーはそのメソッドを許可リストに載った祖先のRBS定義(kindに応じてインスタンスまたはシングルトン)に対してMUST解決します。許可リストのメンバーシップ基準は「このクラスのRBSは権威的かつ完全である ── それが宣言しない呼び出しは正真正銘の誤りである」というもので、Rigor::Plugin::Baseをシードとします(本リポジトリはクラスとsig/rigor/plugin/base.rbsの両方を所有し、libの自己チェックによってロックステップに保たれます)。そして、RBSが省略しているメソッドにそのオブジェクトが応答するようなコア/stdlib/サードパーティのクラスをMUST NOT含めます。連鎖ウォークは、不正な巡回的class A < B / class B < Aソースがループしないよう、訪問済み集合をMUST運びます。許可リストに載った祖先へ到達する解決は、その祖先のRBSに対して直接ディスパッチするのと観測上同一でMUSTあります ── つまり通常のcall.undefined-method / call.wrong-arity規則が継承された契約呼び出しにも適用され、祖先のRBSが運ぶ精度(例えばManifest戻り値型)がコールサイトへ流れます。
解決されたRBSメソッドが複数のオーバーロードを持つとき、Slice 4 phase 2cはRigor::Inference::MethodDispatcher::OverloadSelectorを通じてその1つを選択します。セレクタはMUST以下を行います。
- 位置引数のアリティ(arity)でオーバーロードをフィルタします。実際の
arg_types.sizeはMUSTrequired_positionals.size + trailing_positionals.size <= nを満たし、かつrest_positionalsが存在するかn <= required + optional + trailingのいずれかです。 required_keywordsが空でないオーバーロードをスキップします。Slice 4 phase 2cはキーワード引数を呼び出し位置に通さないため、キーワード必須のオーバーロードは現在の呼び出し形状から到達不能です。- 残ったオーバーロードのうち、すべての(仮引数、実引数)位置ペアについてMUST
param_type.accepts(arg_type, mode: :gradual)を参照します(rest位置引数は1つの宣言を繰り返し消費します)。すべてのペアがyesまたはmaybeを返すとき、オーバーロードはマッチします。 - 宣言順で最初にマッチしたオーバーロードを取ります。どのオーバーロードもマッチしないとき、
method_types.firstにフォールバックします。このフォールバックは「最初のマッチが勝つ」からの唯一の規範的な逸脱です。これは、(untypedに劣化したインターフェイス・ジェネリクス・まだ配線していない呼び出し元のため)実際の引数型がどのオーバーロードにもマッチしない呼び出し位置について、Slice 4 phase 1 / 2bのフェイルソフト契約を保ちます。
実装はパフォーマンスのためにオーバーロードごとに引数型を事前翻訳してMAYかまいませんが、self_typeとinstance_typeの置換はディスパッチ位置に依存するため、(class_name, method_name)キーにまたがって結果をMUST NOTキャッシュしません。
Rigor::Inference::RbsTypeTranslator.translate(rbs_type, self_type:, instance_type:, type_vars:)はRBS::Types::*からRigor::Typeへの唯一の規範的経路です。MUST決定的であり、任意の整形式RBS型でMUST NOTraiseし、docs/adr/4-type-inference-engine.mdに文書化されたマッピングにMUST従います。置換キーワードは独立しています。
Bases::SelfはMUSTself_type:で置換されます。インスタンスディスパッチではNominal[C]、シングルトンディスパッチではSingleton[C]です。Bases::InstanceはMUSTinstance_type:で置換されます。ディスパッチャーは、レシーバーがSingleton[C]であってもdef self.create: () -> instanceがNominal[C]に解決されるよう、ディスパッチ種別に関係なくNominal[C]を渡します。Variable(Slice 4 phase 2d)はMUSTtype_vars:で置換されます。マップはRBS変数のnameシンボル(:Elem・:K・:V…)でキー付けされます。束縛された変数はMUST束縛されたRigor::Type値に置換されます。束縛されていない変数はMUSTDynamic[Top]に劣化します。ClassInstanceはMUST同じtranslate呼び出しを通じてargsを再帰的に翻訳し、::Array[Elem]がNominal["Array", [type_vars[:Elem]]]へラウンドトリップするようにします。兄弟ジェネリック形式(Tuple・Record・Proc)の翻訳器は、Slice 5以降でジェネリック搭載が育てば、同じ再帰規則に従います。- 任意のキーワードはMAY省略でき、対応するRBSトークンは
Dynamic[Top]に劣化します。type_vars:のデフォルトはMUST空ハッシュで、これによりキーワードは非ジェネリック呼び出しに影響しません。
マッピングを精緻化する将来のスライス(交差・インターフェイス・エイリアス解決)は、漸進的型付け軸において既存エントリーの出力をMUST変更しません。精度のいかなる引き締めも、結果型に対する部分型(subtype)クエリへの非破壊変更でMUSTあります。
Slice 4 phase 2dのジェネリックディスパッチ契約はMUSTさらに以下を満たします。
Rigor::Type::NominalはMUST順序付き・フローズンなtype_args配列を運びます。空配列はMUST「素」形式(Array)を表し、空でない配列はMUST適用済みジェネリック(Array[Integer])を表します。2つのキャリアはclass_nameとtype_argsの両方が一致するときのみMUST構造的に等しく比較されます。Rigor::Environment::RbsLoader#class_type_param_names(class_name)はMUSTそのクラスの宣言された型パラメータ名をArray<Symbol>として返します。シングルトンメソッドが同じ名前でパラメータ化されるため、インスタンス定義から取得します。非ジェネリッククラスや未知の名前についてはMUST空配列を返します(フェイルソフト)。- ディスパッチャーは
class_type_param_namesをレシーバーのtype_argsとジップしてtype_varsマップをMUST構築します。空のtype_args(素のレシーバーとシングルトン)は、自由変数が以前と同様に劣化するようMUST空マップを生成します。パラメータと引数のアリティ不一致はMUST空マップを生成します。ディスパッチャーは黙って切り詰めたりパディングしたりしてはMUST NOTなりません。 - ディスパッチャーはMUSTオーバーロードセレクタと最終的な戻り値型翻訳の両方に同じ
type_varsマップを通し、これにより::Array[Elem]のような引数型はArray[Dynamic[Top]]に劣化するのではなく、acceptsチェックの前にElemを置換します。
Slice 5 phase 1のシェイプ(shape)ディスパッチ契約はMUSTさらに以下を満たします。
Rigor::Type::TupleとRigor::Type::HashShapeはディスパッチレシーバーとして使われるとき、MUST基底のnominalキャリアに射影されます。Tuple[T1..Tn]はNominal["Array", [union(T1..Tn)]]に射影されます(空Tupleでは素のArray)。HashShape{k: T,...}はNominal["Hash", [union(constant_keys), union(values)]]に射影されます(空シェイプでは素のHash)。射影はMUSTRbsDispatch.receiver_descriptorに閉じ込められます。キャリア自体のサーフェス契約はMUST値オブジェクトとして薄く保たれます。Rigor::Inference::Acceptanceはシェイプキャリアを射影されたnominalと対称にMUST扱います。- nominalな
selfはシェイプを射影し既存のnominal受理経路を再帰することでシェイプのotherをMUST受理し、これによりNominal[Array, [Integer]].accepts(Tuple[Constant[1], Constant[2]])はNominal[Array, [Integer]].accepts(Nominal[Array, [union(Constant[1], Constant[2])]])と等価で同じ結果を生成します。 Tupleなselfは同じアリティのTupleなotherをMUST要求し、要素ごとに(共変(covariant)で)再帰します。アリティ不一致はMUSTnoに縮退します。TupleでないotherはMUST拒否されます。なぜなら解析器はジェネリックnominal単独からアリティを証明できないからです。HashShapeなselfは、selfのすべての必須キーがotherで必須であることをMUST要求します(共有エントリーでは深さ共変)。selfのオプショナルキーはotherでMAY不在でかまいませんが、存在するときは値が同じ深さチェックをMUST満たします。closedなselfは既知の追加キーとオープンな追加キーソースをMUST拒否します。openなselfはより広いシェイプをMAY受理します。必須キーの欠落はMUSTnoに縮退します。HashShapeでないotherはMUST拒否されます。nominal側の射影はaccepts_nominal経路に存在し、HashShape経路には存在しません。
- nominalな
Rigor::Inference::RbsTypeTranslatorはRBS::Types::TupleとRBS::Types::Recordを専用のシェイプキャリアにMUSTマップします(Nominal[Array]/Nominal[Hash]にではありません)。要素型とフィールド型は呼び出し元のself_type:/instance_type:/type_vars:コンテキスト下で再帰的にMUST翻訳され、これによりタプルやレコード内のジェネリクスが境界を越えて生き残ります。Recordは必須フィールドをRBS::Types::Record#fieldsから、オプショナルフィールドを#optional_fieldsからMUST翻訳し、結果のHashShapeをclosedとMUSTマークします。Rigor::Inference::ExpressionTyperは、すべての要素が非splat値である空でない配列リテラルをTupleキャリアにMUSTアップグレードします。splatを含むリテラルは、[*xs, 1]が依然として推論可能な要素型を生成するよう、Slice 4 phase 2dのNominal[Array, [union]]形式をMUST保ちます。ExpressionTyperは、すべてのエントリーが静的なSymbolNodeまたはStringNodeキー(非nilのvalue/unescapedを持つ)のAssocNodeであるハッシュリテラルをHashShapeキャリアにMUSTアップグレードします。AssocSplatNodeエントリー・動的キー・重複キーを持つリテラルはMUSTNominal[Hash, [K, V]]形式にフォールスルーします。整数端点のRangeNodeリテラルは、シェイプディスパッチがタプル範囲スライスを解決できるよう、Constant[Range]としてMUST運ばれます。動的範囲はMUSTNominal[Range]のままです。
呼び出しのレシーバーがRigor::Type::Dynamicで正のディスパッチャーティアがどれもマッチしないとき、ExpressionTyper#call_type_forはFallbackTracerイベントを記録せずに静かにMUSTDynamic[Top]を返します。これは認識されたセマンティック結果であり — value-lattice.mdの値格子代数はDynamicが不透明なメソッド呼び出しを通じて伝播することを要求します — フェイルソフト的妥協ではありません。Dynamicでないレシーバーは、規則が解決されないときには依然として標準のフェイルソフトフォールバック(トレーサーイベント付き)をトリガーします。
Slice 5 phase 2のシェイプ対応ディスパッチティア(Rigor::Inference::MethodDispatcher::ShapeDispatch)は、TupleとHashShapeのレシーバーが要素アクセスメソッドを射影されたArray#[] / Hash#fetchの答えではなく位置ごと/キーごとの精密な型に解決するよう、定数畳み込みティアとRBSバックティアの間でMUST動作します。ティアは以下のカタログをMUST処理し、呼び出しを静的シェイプに対して証明できないときnilを返してRbsDispatchに委ねます。
- Tupleレシーバー:
first・last・size/length/count(無引数・無ブロック)はMUST精密なタプル要素/Constant[size]を返します。単一のConstant[Integer]引数を伴う[]とfetchは、負のインデックスを長さで正規化しMUSTインデックスが[-size, size)にあるとき精密な要素を返します。範囲外のインデックスはMUST委ねます(nil)。これによりfetch(は実行時にraiseするでしょう)に対しては射影の答えが適用され、チェーンステップを通じて呼ばれたとき(後述のdigを参照)にのみMUSTConstant[nil]に解決されます。Constant[Range]の整数/nil端点を伴う[]と2つのConstant[Integer]引数を伴う[]はRubyのArray#[]スライスセマンティクスをMUST用い、スライスされたTupleまたは静的にnilなスライスについてConstant[nil]を返します。fetchはそれらの形式をMUST NO要求しません。digは解決されたメンバーを通じてMUST再帰します。Tuple/HashShapeのメンバーは残りの引数でカタログにMUST再ディスパッチします。Constant[nil]メンバーはチェーンをMUST短絡します(RubyのArray#digは実行時に同じことをします)。他のConstantsと任意の非シェイプキャリアはMUST委ね、射影の答えが適用されます。チェーンステップ中に発生する範囲外インデックスは、RubyのArray#digが範囲外インデックスに対してraiseするのではなくnilを返すため、MUSTConstant[nil]に解決されます。 - HashShapeレシーバー:
size/length(無引数)は、シェイプがclosedで既知のキーがどれもオプショナルでないときにのみMUSTConstant[size]を返します。単一のConstant[Symbol|String]引数を伴う[]とfetchは、必須キーが宣言されているときMUST精密な値を返します。オプショナルキーの[]/digはMUSTvalue | nilを返しますが、オプショナルキーのfetchは、キーが不在の可能性があり、RubyがKeyErrorをraiseするためMUST委ねます。欠落キーは[]について(Rubyの実行時挙動と一致して)MUSTConstant[nil]に解決され、fetchについてはMUST委ねます。digはTupledigと同じチェーンセマンティクスにMUST従います。すなわち単一の静的キーは精密な値(または欠落キーに対してはConstant[nil])に解決されます。複数キーチェーンは解決された値を新しいレシーバーとして再帰します。1つ以上のConstant[Symbol|String]引数を伴うvalues_atはMUST位置ごとの値がキーごとの値であり欠落キーがConstant[nil]で埋められたTupleを返します。任意の引数が非静的のときや、呼び出しが引数ゼロのときはフォールスルー(委ねる)します。
シェイプティアはRBS環境をMUST NOT参照し、任意の入力でMUST NOTraiseし、フォールバックトレーサーにMUST NOT触れません。これは型キャリアの上に重ねられた純粋な精緻化です。カタログ外のメソッド・非静的なキー/インデックス・非整数範囲・チェーンステップが静的に解決できない任意のdig/values_at呼び出しはMUST委ね、射影ベースのRbsDispatchの答えが適用されます。
この分離は規範的です。すなわち実装はいかなるRigor::Type形式の演算子メソッド対応サブクラスをMUST NOT定義しません(例えば、+/*規則を運ぶ仮想的なRigor::Type::IntegerType)。演算子セマンティクスはディスパッチャーが参照するメソッドハンドラエントリーとしてMUST表現されます。組み込み算術のために型クラスを特殊化することは、型格子とメソッドセマンティクスを独立に拡張可能に保つために拒絶されます。
ローカル変数
Section titled “ローカル変数”ローカル変数リードノード(Prism::LocalVariableReadNode)はMUSTレシーバースコープでルックアップされます。束縛された名前はMUST束縛されたRigor::Typeを返します。束縛されていない名前は上記の規則に従いMUSTDynamic[Top]へフェイルソフトします。Scope#type_ofは束縛されていないローカルでMUST NOTraiseします。
ローカル変数ライトノード(Prism::LocalVariableWriteNodeとそれを含意するターゲット)はMUSTその値式の型として型付けされます。結果をスコープへ束縛し直すことは文レベル評価器の責任であり(docs/adr/4-type-inference-engine.mdのSlice 3を参照)、Scope#type_of自体はスコープをMUST NOT変更しません。
文レベル評価
Section titled “文レベル評価”Rigor::Scope#type_ofは純粋な式レベルクエリであり、スコープをMUST NOTスレッドしません。文レベル評価器Rigor::Inference::StatementEvaluator(Slice 3 phase 2)はその隣にあり、補完的なスコープスレッディングサーフェスを提供します。Rigor::Scope上の公開デリゲートはMUST存在します。
Rigor::Scope#evaluate(node, tracer: nil) #=> [Rigor::Type, Rigor::Scope]契約はMUST以下を満たします。
- 返されたペアの最初の要素はMUST
nodeが生成する型で、純粋な式についてScope#type_of(node)が返すものと等価です。2番目の要素はMUSTnodeが実行された後に観測可能なスコープです。スコープ効果を行わないノードについて、これはMUSTレシーバースコープです(==で比較され、レシーバーの同一性はMAY異なります)。 - レシーバースコープはMUSTいかなる場合も変更されません。内部の再帰は、分岐が分離され区別された分岐出力の等価性が観測可能であるよう、フォークされた各スコープについてMUST新鮮な
StatementEvaluatorインスタンスを割り当てます。 tracer:キーワードはMUSTすべてのネストされたScope#type_of呼び出しにスレッドされ、これにより文的ノードの子を型付けする間に発出されるフェイルソフトフォールバックが同じトレーサー下に記録されます。- 評価器が特殊化していないノードに対する
evaluate呼び出しは、MUSTScope#type_of(node, tracer:)に委ね、MUSTレシーバースコープを変更せずに返します。これによりSlice 1のフェイルソフトポリシーが保たれます。すなわち認識されない文的ノードはMUST NOTraiseしません。
Slice 3 phase 2で評価器がMUST認識するノードのカタログは以下のとおりです。
Prism::ProgramNodeとPrism::StatementsNode— 宣言順にすべての子文を通じてスコープをスレッドする逐次評価です。本体の値はMUST最後の文の型(空本体ではConstant[nil])です。後置スコープはMUST最後の文の後置スコープです。Prism::LocalVariableWriteNode— エントリースコープ下で右辺値を評価し、Scope#with_localを介してnameを結果型に束縛します。ペアの型はMUST右辺値の型と等しくなります。Prism::MultiWriteNode(Slice 5 phase 2 sub-phase 2) — エントリースコープ下で右辺を一度評価し、それから分解木(lefts/rest/rights、restはPrism::SplatNodeでexpressionはMAYPrism::LocalVariableTargetNode)を歩き、Rigor::Inference::MultiTargetBinderを介してすべての名前付きローカルを束縛します。ペアの型はMUST右辺の型と等しくなります(Rubyの(a, b = [1, 2]) #=> [1, 2]セマンティクスと一致)。右辺がType::Tupleのとき、スロットごとの束縛はMUST要素ごとです。すなわち欠落した前方/後方スロットはConstant[nil]に縮退し、restスロットは中間要素のType::Tuple(ソースに余剰がないときTuple[])に束縛されます。Tupleでない右辺については、すべてのスロットがMUSTDynamic[Top]に束縛されます。ネストされたPrism::MultiTargetNodeターゲットはMUST同じバインダーを通じて再帰します。非ローカルなターゲット(インスタンス/クラス/グローバル変数、定数、インデックス/呼び出しターゲット、無名splat)はMUST静かにスキップされます。Prism::IfNodeとPrism::UnlessNode— 述語をまず評価し(その後置スコープは両方の分岐で共有されます)、それから後置述語スコープ下で各分岐を評価します。結果型はMUST2つの分岐型のunionです。後置スコープはMUST2つの分岐スコープのnil注入付き結合です(後述)。nil分岐(else/thenなし)はMUSTConstant[nil]と後置述語スコープを寄与します。Prism::ElseNode— レシーバースコープ下で本体を評価し、空本体については[Constant[nil], scope]を返します。Prism::CaseNodeとPrism::CaseMatchNode— 述語をまず評価します。すべてのWhenNode/InNode本体とオプショナルなelse節は後置述語スコープ下で独立に評価され、N分岐に一般化された同じnil注入付き結合規則でマージされます。Prism::WhenNodeとPrism::InNode— レシーバースコープ下で文を評価します。空本体はMUST[Constant[nil], scope]です。Prism::BeginNode— 主経路(本体、それからオプショナルなelse節。else節はMUST本体の値を置き換えますが、elseの前に本体が実行されたため本体のスコープ効果は依然として適用されます)を評価します。チェーン内の各Prism::RescueNodeはエントリースコープ下で評価される代替の出口経路です。出口型はMUST主経路とrescue出口のunionです。出口スコープはMUST主経路とrescueスコープのnil注入付き結合です。ensure_clauseが存在するとき、そのスコープ効果はMUST結合された出口スコープの上に重ねられ、これによりensure内のみで束縛されたローカルが観測可能なまま残ります。ensureの値はMUST NOT出口型に寄与しません。Prism::WhileNodeとPrism::UntilNode— 述語を評価し(その後置スコープは本体で観測可能です)、それから本体を評価します。結果型はMUSTConstant[nil]です。後置スコープはMUST後置述語スコープと後置本体スコープのnil注入付き結合で、「本体は0回以上実行されたかもしれない」をモデル化します。Prism::AndNodeとPrism::OrNode— LHSを評価し、それからLHSの後置スコープ下でRHSを評価します。結果型はMUST2つのオペランド型のunionです。後置スコープはMUSTLHSとRHSの後置スコープのnil注入付き結合(「LHSは常に実行された。RHSはときどきしか実行されなかった」をモデル化)です。Slice 3 phase 2はLHSの真偽値性でナローイングしません。その精緻化はSlice 6の仕事です。Prism::ParenthesesNode— 内側の式を通じてスコープをスレッドし、これにより(x = 1; x + 2)がxを束縛してConstant[3]を生成します。Prism::ClassNodeとPrism::ModuleNode— 本体を新鮮なスコープで評価します(Rubyのクラス/モジュールスコープは外側のローカルを見ません。Environmentのみを共有します)。本体の値は本体の最後の文(空本体についてはConstant[nil])です。クラス/モジュール定義は囲みスコープ内のいかなるローカルも束縛しないため、後置スコープはMUSTレシーバースコープを変更せずに残します。評価器は本体の評価のためにMUST新しいレキシカルクラスフレームをclass_contextにプッシュします。ネストされたdefは、RBSルックアップを解決するためにそのフレームを参照します。フレームの修飾名はMUSTレンダリングされたconstant_pathです(例えばclass Foo::Barに対して"Foo::Bar"、class A; class Bに対しては全ネスト名のジョイン)。Prism::SingletonClassNode— 同じ新鮮スコープ契約です。シングルトン式がselfのとき、最内のレキシカルクラスフレームは本体の評価のためにMUSTsingleton: trueにフリップされ、これによりclass << self内のdef fooがRbsLoader#singleton_methodを通じて解決されます。selfでない式についてはレシーバークラスは静的に解決可能ではありません。評価器は既存のクラスコンテキストをMUST変更せずに保ち、ネストされたdefがDynamic[Top]の引数デフォルトに劣化することを受け入れます。Prism::DefNode—Rigor::Inference::MethodParameterBinder(後述)を介してすべての名前付き引数を束縛することでメソッドエントリースコープを構築し、そのスコープ下で本体を評価します。ペアの型はMUSTConstant[:method_name](defがSymbolに評価されるRubyの実行時挙動と一致)であり、ペアのスコープはMUSTレシーバースコープを変更せずに残します(defは囲みスコープ内に束縛を導入しません)。本体は外側スコープのローカルをMUST NOT見ません。
Self型付け(Slice A-engine)
Section titled “Self型付け(Slice A-engine)”Rigor::Scopeはオプショナルなself_type:フィールドを運び、Scope#self_typeを通じてアクセスされ、Scope#with_self_type(type)を介して更新されます。フィールドはトップレベルスコープではnilであり、Rubyがselfに確定的な同一性を与える境界でStatementEvaluatorによって注入されます。
Prism::ClassNode/Prism::ModuleNode本体:self_typeはMUSTSingleton[<qualified-name>]です(クラス本体内のselfはクラスオブジェクト自体だからです)。- レシーバーが
selfであるPrism::SingletonClassNode本体: 上と同じ(本体は依然としてself= 囲みクラスオブジェクトで実行されます)であり、最内クラスフレームはさらにsingleton: trueにフリップします。 Prism::DefNode本体: defがシングルトン側(def self.fooやclass << selfの中のレキシカルなdef)にあるとき、または周囲のクラスフレームがすでにシングルトンであるとき、self_typeはMUSTSingleton[<qualified-name>]です。それ以外(通常のインスタンスdef)では、self_typeはMUST型引数なしのNominal[<qualified-name>]です。トップレベルのdef(囲みクラスなし)はMUSTself_typeをnilのまま残します。
ExpressionTyperはそのフィールドを2か所で消費します。
Prism::SelfNodeはMUST設定されているときscope.self_type、nilのときDynamic[Top]として型付けされます。これはどちらの場合もMUST NOTフォールバックイベントを記録しません。receiver: nilのPrism::CallNode(attr_reader_method・private_helper…のような暗黙self呼び出し)は、MUSTMethodDispatcher.dispatchのレシーバー型としてscope.self_typeを採用します。self_typeがnilのとき、レシーバーはnilのままで、既存のトップレベルフォールバックが適用されます。
Scope#==とScope#hashはMUSTself_typeを含めます。Scope#joinは両側が一致するときMUSTself_typeを保ち、異なるときMUSTnilにリセットします。Scope#with_local・Scope#with_fact・Scope#with_self_typeはMUSTすべて他の2つのフィールドを保ちます。
インスタンス・クラス・グローバル変数の束縛(Slice 7 phase 1)
Section titled “インスタンス・クラス・グローバル変数の束縛(Slice 7 phase 1)”Rigor::Scopeはlocalsに加えて3つの追加の束縛マップを運びます。すなわちivars・cvars・globals(それぞれHash[Symbol, Type]、デフォルト空・フローズン)です。アクセサは以下のとおりです。
Scope#ivar(name)・#cvar(name)・#global(name)—nameの束縛された型を返すか、解析されたプログラムのスライスに書き込みが記録されていなければnilを返します。Scope#with_ivar(name, type)・#with_cvar・#with_global— 名前付き束縛が追加された新鮮なスコープを返します。with_localが行うのと正確に同様にlocals・fact_store・self_type・declared_typesをMUST保ちます。
StatementEvaluatorはPrism::InstanceVariableWriteNode・Prism::ClassVariableWriteNode・Prism::GlobalVariableWriteNodeをMUSTLocalVariableWriteNodeと対称に処理します。すなわちエントリースコープ下で右辺値を評価し、[rvalue_type, post_rvalue_scope.with_<kind>(name, rvalue_type)]を返します。複合書き込み形式(@x ||= …・@x &&= …・@x += …)は、後続のスライスが後置スコープでそれらを束縛するまで、既存のtype_of_assignment_write式型付け器経路に残ります。ExpressionTyperはPrism::InstanceVariableReadNode・Prism::ClassVariableReadNode・Prism::GlobalVariableReadNodeを、対応するスコープ束縛マップを参照しMUST処理し、束縛が存在しないとき(フォールバックイベントを記録せずに)Dynamic[Top]へフォールスルーします。
Scope#joinはivar/cvar/グローバル束縛をローカルをunionするのと同じ方法でMUSTunionします(両側で束縛されていない任意の名前は落とします。半束縛の名前のnil注入は、カタログがそれを必要とする制御フロー構成へ拡張されたときに文レベル評価器の責任です)。Scope#==とScope#hashはMUST3つの束縛マップを含めます。
Slice 7 phase 2はivarのメソッドローカル境界を持ち上げます。すなわちScopeは修飾クラス名でキー付けされたclass_ivars: Hash[String, Hash[Symbol, Type]]アキュムレータを運びます。ScopeIndexerは、すべてのPrism::ClassNode / Prism::ModuleNodeを歩き、ネストされた各インスタンスPrism::DefNodeに降下し、適切なself_typeを運ぶスコープ下で各Prism::InstanceVariableWriteNode右辺値を型付けする別個の前パスを通じて、インデックス時に1度だけそれを埋めます(インスタンスdefのみ — シングルトンdefは異なるselfで動作します)。クラス内で同じivarへの複数書き込みはType::Combinator.unionを介してunionされます。StatementEvaluator#build_method_entry_scopeはインスタンスメソッド本体のivarsをMUSTScope#class_ivars_for(current_class_path)からシードします。シングルトンメソッド本体(def self.fooやclass << self内のレキシカルなdef)は、selfがクラスオブジェクト自体であるため、アキュムレータをMUST NOT参照しません。
前パスはローカル束縛なしで右辺値を型付けするため、@x = 1はConstant[1]を記録しますが、@x = some_local + 1はDynamic[Top]を記録します。Cvarsとグローバルはメソッドローカルのままです。同等のクラスレベル/プロセスレベルのアキュムレータは後続のスライスです。
書き込み前読み取りnilゲートと追加のイニシャライザー(ADR-38)
Section titled “書き込み前読み取りnilゲートと追加のイニシャライザー(ADR-38)”付随する前パスが各インスタンスメソッド本体をAST(実行)順に歩き、最初の参照が読み取りであるivar名(書き込み前読み取り)を追跡します。それらの名前はクラス全体のread_before_writeアキュムレータにunionされ、確定時にクラスはそれぞれにConstant[nil]を貢献します——「メソッドが@xへの書き込みの前に@xを読む」形のための健全性で、Rubyはnilを返します。
initializeは組み込みの例外です: initialize本体については、すべてのivar書き込みターゲットが書き込み前読み取りの証拠を貢献する代わりにクラスのinit_writesセットに折り込まれるので、コンストラクタが代入を保証するivarが兄弟リーダーでnilへの幅広げを受けません。
ADR-38はその例外をプラグイン宣言の追加のイニシャライザーへ一般化します。Inference::ScopeIndexerはこの単一のゲートで集約されたPlugin::Registry#additional_initializersセットを参照します: エントリーがcovers_method?である名前を持つdefで、かつその囲みクラスがエントリーのreceiver_constraintに等しいか継承する(Environment#class_ordering経由でマッチ、ADR-16ティアAが使うのと同じメカニズム)ものについて、メソッドのivar書き込みはinitializeのものとまさに同じようにinit_writesに折り込まれます。これはPHPStanのAdditionalConstructorsExtensionのRuby版です——Minitest::Test#setup、Railsのafter_initialize、または本体が走る前にフレームワークが呼び出すivar状態を確立するDIセッター。値オブジェクトのシェイプ(shape)はplugin.md(additional_initializers:)で規定されています。
ルックアップは前パススコープのEnvironmentからプラグインレジストリを読みます;エンジンは解決全体を全面的にフェイルソフトとMUST扱います(あらゆるエラー、または欠落/空のレジストリは「マッチなし」に劣化します)。これは構成上健全です: ゲートはnil貢献を抑制することしかできず、決して追加しないので、見逃しや広すぎるマッチは偽陽性安全です——解析器をより厳格にすることは決してなく、既存のnil幅広げをそのまま残すだけです(ADR-38 §「Why this is FP-safe」)。
Slice 7 phase 3はStatementEvaluatorを、すべての変数種別の複合書き込みで拡張します。ハンドラはPrism::{Local,InstanceVariable,ClassVariable,GlobalVariable}{Or,And,Operator}WriteNodeをカバーし、統一的なセマンティクスを適用します。
- 現在の型は適切なスコープ束縛マップ(
local/ivar/cvar/global)から読まれます。束縛されていない変数はDynamic[Top]として読まれます。 - 右辺値は
sub_evalを通じてエントリースコープ下で評価されます。 ||=の結果型はunion(Narrowing.narrow_truthy(current), rhs)です。&&=ではunion(Narrowing.narrow_falsey(current), rhs)です。演算子形式(+=・-=・*=…)では、型付け器はMethodDispatcherを通じてcurrent.send(operator, rhs)をディスパッチし、ミス時にDynamic[Top]にフォールバックします。- 変数はそれから、平のライトハンドラが用いるのと同じ
with_*ビルダーを通じて後置スコープに再束縛されます。式の値はRubyのセマンティクスと一致して結果型です。
レキシカル定数ルックアップ(Slice A constant-walk)
Section titled “レキシカル定数ルックアップ(Slice A constant-walk)”ExpressionTyper#type_of_constant_readと#type_of_constant_pathは、解析器が静的に証明できる粒度でRubyの実行時定数ルックアップ規則をミラーリングし、周囲のレキシカルクラスコンテキストを歩いて定数をMUST解決します。
- 修飾候補
<class_path>::<name>がMUST最初に試されます。ここで<class_path>はScope#self_typeがType::NominalまたはType::Singletonのときのそのclass_nameです。 - それから候補パスはMUST
::segmentごとに1つずつ剥がされて再試行されます(<class_path>::<name>→<parent_path>::<name>→ … →<top>::<name>)。 - 素の
<name>がMUST最後に試され、ウォーク前のトップレベル挙動を保ちます。 - 各候補について、型付け器はMUSTまず
Environment#singleton_for_name(candidate)(クラスオブジェクト →Type::Singleton[candidate]を解決)、それからEnvironment#constant_for_name(candidate)(非クラスのRBS定数宣言を翻訳されたRigor::Typeに解決)を参照します。最初のヒットが勝ちます。その候補で両方がミスしたときのみウォークが続きます。 - どちらのクエリでも候補が解決されないとき、エンジンは以前と同様にMUST
fallback_for(node, family: :prism)へフォールスルーします。
Environment#constant_for_name(name)は取り付けられたRbsLoaderをMUST参照し、ローダーが不在のときや名前が定数宣言を持たないときnilを返します。RbsLoader#constant_type(name)はRBS::AST::Declarations::Constant#typeをRigor::Inference::RbsTypeTranslator.translateを通じてMUST翻訳し、結果のRigor::Typeを返すか、翻訳がType::Bot(空型)を生成するときnilを返します。クエリは不正な入力でMUST NOTraiseしません。ローダーはフェイルソフトのままです。
宣言位置のオーバーライド(Slice A-declarations)
Section titled “宣言位置のオーバーライド(Slice A-declarations)”Rigor::Scopeはdeclared_types:フィールドを運びます。これは特定のノード同一性についてExpressionTyper#type_ofをオーバーライドする同一性比較のHash[Prism::Node, Rigor::Type]です。デフォルト値はフローズン空ハッシュです。Scope#with_declared_types(table)は提供されたテーブルを運ぶスコープを返します。Scope#with_local・Scope#with_fact・Scope#with_self_type、およびjoinヘルパーはMUST構造的参照によってテーブルを保ちます。
ExpressionTyper#type_of(node)はMUST他の任意のディスパッチの前にscope.declared_types[node]を参照します。値が存在するとき、型付け器はMUSTそのまま返し、MUST NOTフォールバックイベントを記録しません。
Rigor::Inference::ScopeIndexer.index(root, default_scope:)は宣言位置のノードについてMUSTテーブルを埋めます。
Prism::ModuleNode#constant_pathはMUSTSingleton[<qualified-path>]にマップされます。ここで<qualified-path>は囲む各ModuleNode/ClassNodeの宣言名のジョインです。Prism::ClassNode#constant_pathはMUST対応するSingleton[<qualified-path>]にマップされます。- オーバーライドはMUST最外の
constant_pathノードのみをカバーします。class Foo::Barについて、内側のFoo参照は実際のルックアップのままです(レキシカルウォークを通じて解決されます)。
indexが生成するシードされたスコープはMUST埋められたテーブルを運び、これによりStatementEvaluatorのクラス本体およびメソッド本体の新鮮スコープ(Scope.empty(environment: ...)に続いてwith_self_typeとwith_declared_types)がオーバーライドをネストされた本体へ伝播します。インデクサーが生成したスコープは、オーバーライドが実際に発火するよう、Rigor::Scope#type_ofの呼び出し元(CLIプローブ・テストフィクスチャ)によってMUST用いられます。素のRigor::Scope.emptyスコープは、テスト分離のために空テーブル挙動を意識的に保ちます。
トップレベルスコープ(self_typeが設定されていない)は、クラスコンテキストを設定しないテストでウォーク前の挙動が観測可能であるよう、MUST素の候補のみを生成します。ウォークはレシーバースコープをMUST NOT変更せず、解決に失敗する名前でMUST NOTraiseせず、シングルトンの祖先チェーンをMUST NOT走査しません — その精緻化は将来のスライスです。
nil注入付き結合
Section titled “nil注入付き結合”Scope#joinは一方のレシーバーにのみ束縛されている名前を落とします(上記の不変スコープ規律に従う)。文レベル評価器の分岐マージは、結合スコープがT | nilとしてそれらを見るよう、半束縛の名前について代わりにMUSTConstant[nil]を注入します。
scope_aに束縛されているがscope_bには束縛されていない名前について: 結合前にscope_b内でそれらの名前をConstant[nil]に束縛します。scope_bに束縛されているがscope_aには束縛されていない名前について: 結合前にscope_a内でそれらの名前をConstant[nil]に束縛します。- それから拡張されたスコープに対して
Scope#joinを呼び出します。結果はMUSTいずれかの側からのすべての名前を含み、unionは一方の側にのみ束縛されている名前についてConstant[nil]を含みます。
これはSlice 3 phase 1の不変スコープ規律が文レベル評価器に委ねる契約です。N項分岐マージ(case/when・begin/rescueチェーン)は繰り返しのペアごとのnil注入付き結合で還元されます。Scope#joinの下でnil注入はunionと交換するため、還元順序は結果に影響しません。
ノードごとのスコープインデックス
Section titled “ノードごとのスコープインデックス”Rigor::Inference::ScopeIndexer.index(root, default_scope:)は、Prismプログラムのサブツリーをノードごとのスコープルックアップに変換する標準サーフェスです。MUST以下を満たします。
- 戻り値はMUST同一性比較の
Hash{Prism::Node => Rigor::Scope}です。rootのサブツリーに含まれないノードをルックアップするとMUSTdefault_scope(Hash#defaultスロット)を生成します。 evaluate(root)の間にStatementEvaluatorが訪れる各Prismノードについて、インデクサーはMUSTその訪問時に観測されたエントリースコープを記録します。訪問は、インデクサーが新鮮な評価器に配線するon_enter:コールバックを通じて発火されます(したがってStatementEvaluatorは状態フリーのままです。インデクサーがテーブルを運びます)。rootのサブツリーでStatementEvaluatorが訪れないPrismノード(評価器のデフォルト分岐がフォールスルーしたノードの式内部の子)について、インデクサーはMUST記録されたスコープを最も近い記録された祖先のスコープに設定します。DFS事前順伝播は契約です。すなわち子はMUST親と少なくとも同程度に情報量のあるエントリースコープを観測し、決して弱くありません。- インデクサーは内部のStatementEvaluatorをMUST
tracer: nilで実行します。フェイルソフトフォールバックイベントを欲しがるCLI呼び出し元は、イベントがちょうど第2パスから来てインデクサー自身のプログラムツリー型付けによって二重記録されないよう、MUSTインデックス後のtype_ofプローブにのみトレーサーを取り付けます。
CLIコマンドrigor type-ofとrigor type-scanは、パースされたファイルからノードを型付けするときMUSTインデックスを参照し、これによりプログラムフロー内で先に束縛されたローカルが、後のノードを型付けするのに使われるスコープへ流れ込みます。両コマンドはindex[node]をルックアップし、それからnode_scope.type_of(node, tracer:)を実行します。上記の契約がこの合成を正しくするものです。
クロスファイルのメソッド発見(ADR-24)
Section titled “クロスファイルのメソッド発見(ADR-24)”RBSを持たないユーザー定義クラスに対する暗黙的self呼び出しを解決するため、Rigor::Inference::ScopeIndexerは境界のあるプロジェクト事前パスを走らせ、発見したテーブルを派生スコープへ通します。Rigor::Scopeはそれらを修飾クラス名でキー付けされた純粋なクエリとして公開します:
Scope#user_def_for(class_name, method_name)— インスタンスメソッドのPrism::DefNode、またはnil。Scope#user_def_site_for(class_name, method_name)— クロスファイル事前パスが投入したときの定義位置を"path:line"で(CheckRulesがクロスファイルのモンキーパッチ診断を定義ファイルに向けられるように)、またはnil。Scope#top_level_def_for(method_name)— ファイルスコープ(トップレベル)のdefのPrism::DefNode、またはnil;どのクラス本体の外側でもない暗黙的self呼び出しに使う。Scope#superclass_of(class_name)—class Foo < Barの書かれたとおりの超クラス名、またはnil;ExpressionTyperが呼び出しサイトのレキシカルネスティングで修飾クラスへ解決する。Scope#includes_of(class_name)— クラスがinclude/prependするモジュール名の配列、書かれたとおり;mixinチェーンを通じた暗黙的self呼び出しの解決に使う。Scope#discovered_method_visibility(class_name, method_name)— ユーザー定義メソッドの:public/:private/:protected、またはnil;def.method-visibility-mismatchとdef.override-visibility-reduced(ADR-35)ルールが消費する。Scope#with_discovered_superclasses(table)/#with_discovered_includes(table)— これらのテーブルを派生スコープへ前方に通すビルダー。
Scope#toplevel?はスコープが囲むクラス/モジュール本体を持たない(self_typeがnil)ときtrueを返す。トップレベル限定のcall.unresolved-toplevelルール(ADR-34)をゲートする: ファイル先頭の未解決の暗黙的self呼び出しは警告し、同じミスがクラス/def本体内ならADR-24 WD3に従い寛容なままとなる。
Rigor::Inference::StatementEvaluator#initialize(on_enter:)
Section titled “Rigor::Inference::StatementEvaluator#initialize(on_enter:)”StatementEvaluatorの3つ目のコンストラクタキーワードは、ScopeIndexerが駆動するフックです。MUST以下を満たします。
on_enter:はデフォルトでnilです。nilのとき、コールバックは発火せず、評価器の挙動はMUSTキーワードなしで構築されたslice 3 phase 2評価器と観測可能に同一です。- 非
nilのとき、コールバックはMUSTすべてのevaluate(node)呼び出しの開始時に、ハンドラディスパッチの前に、(node, scope)を引数としてちょうど1回呼ばれます —nodeは入っているPrismノード、scopeはエントリースコープ(その再帰レベルでの@scope)です。 - コールバックはMUSTすべての再帰的な
sub_evalを通じてスレッドされ、フォークされたスコープでのネストされた呼び出しが依然としてそれぞれのエントリーを報告します。
メソッドパラメータ束縛
Section titled “メソッドパラメータ束縛”Rigor::Inference::MethodParameterBinder.new(environment:, class_path:, singleton:)は、DefNodeからメソッドエントリースコープを構築する標準サーフェスです。MUST以下を満たします。
bind(def_node)はMUST、defの引数リストに名前が現れる順で、引数名を束縛型へマップする順序付きHash{Symbol => Rigor::Type}を返します。- 無名引数(識別子のない
*と**)は束縛するローカル名がないため、MUST静かにスキップされます。 class_path:がnilのとき、環境がRBSローダーを持たないとき、または解決されたクラス/メソッドがRBSに知られていないとき、すべての引数はMUSTデフォルトでDynamic[Top]です。バインダーはRBSミスでMUST NOTraiseしません。Slice 1のフェイルソフトポリシーからのフェイルソフト契約は束縛境界でも適用されます。- RBSルックアップが成功したとき、すべての引数スロットはMUSTそのスロットを持つすべてのオーバーロードにわたるマッチングRBS引数型のunionに束縛されます。スロットを省略するオーバーロード(例:
Array#firstの()オーバーロード対def first(n)のn引数がマッチする(?int)オーバーロード)はMUSTDynamic[Top]を寄与するのではなく静かにスキップされます(これにより束縛は、呼び出し元がどのオーバーロードを選ぶか知る必要なく、シグネチャが提供する最も情報量のある型になります)。 - 位置スロット(required・optional・rest・trailing)はMUSTマッチングRBS位置リストへ位置でマッチされます。キーワードスロット(required・optional)はMUSTrequiredとoptionalの両方のキーワードマップにわたって名前でマッチされ、これにより
def foo(by:)の再定義がRBSオーバーロードの?by:キーワード(またはその逆)を拾います。 *rest引数の束縛型はMUSTNominal["Array", [T]]で、Tは翻訳されたrest要素型です。**kw_rest引数の束縛型はMUSTNominal["Hash", [Nominal["Symbol"], V]]で、Vは翻訳されたrestキーワード値型です。バインダーはMUST NOT rest引数を単一要素型に束縛しません — ローカルは実際には配列/ハッシュを保持します。def_node.receiverがPrism::SelfNodeであるまたはsingleton:がtrueのとき、バインダーはMUSTRbsLoader#singleton_methodを参照します(直接の囲みレキシカルスコープはシングルトンクラスです)。それ以外ではMUSTRbsLoader#instance_methodを参照します。翻訳器のself_type:とinstance_type:キーワードはMUSTシングルトン経路では(Singleton[C], Nominal[C])に、インスタンス経路では(Nominal[C], Nominal[C])に設定されます。- パススコープのプロトコル契約 — 提供側(ADR-28)。バインダーが非
nilのソースパスで走り、ロードされたプラグインが(Environment#plugin_registry.contracts_for_path経由で)path_globがファイルにマッチし・method_nameがdefにマッチし・singletonフラグがdefのシングルトン性にマッチするPlugin::ProtocolContractを貢献するとき、バインダーは契約された各位置スロットの束縛をMUST契約が宣言したパラメータ型で置き換えます。型名はEnvironment#nominal_for_nameを通じて遅延的に、束縛時点で解決されます;解決不能な名前(プロトコルのRBSがロードされていない)はMUST前のティアが束縛したものへフォールスルーします——フェイルソフトで、決してraiseしません。これは、パス慣習的なメソッド(request引数が暗黙にRack::Requestであるlib/controller/**/*.rbアクション)が、RBSや引数が何の注釈もしていなくても、そのプロトコル型で解析される方法です。付随するチェック側——メソッドが存在しその推論された戻り値がreturn_type_nameに適合することの確認——はエンジンのルールではなく貢献するプラグイン自身の#diagnostics_for_fileの責任です;値オブジェクトのシェイプ(shape)はplugin.md(protocol_contracts:)にあります。
多重ターゲットバインダー(Slice 5 phase 2 sub-phase 2)
Section titled “多重ターゲットバインダー(Slice 5 phase 2 sub-phase 2)”Rigor::Inference::MultiTargetBinderは、タプル形状の右辺型をPrism多重ターゲットツリーに対して分解する標準サーフェスです。これは純粋なモジュール関数モジュール(状態なし)で、MUST以下を満たします。
MultiTargetBinder.bind(target_node, rhs_type)—Prism::MultiWriteNode(文レベルのa, b = rhs)またはPrism::MultiTargetNode(ネストされた(a, b)形式)のいずれかを受け付けます。分解で束縛された名前付きローカルを宣言順でキーとする順序付きHash{Symbol => Rigor::Type}を返します。rhs_typeがType::Tupleのとき、バインダーは要素を前方(lefts)・中央(rest)・後方(rights)の領域にMUST分解します。インデックスiの前方ターゲットはMUSTtuple.elements[i]に、またはインデックスがタプルの境界を超えるときConstant[nil]に束縛されます。restターゲット(LocalVariableTargetNode式を持つPrism::SplatNode)はMUSTType::Tuple[*middle]に束縛されます(ソースに余剰要素がないときmiddleは空)。後方ターゲットはMUST対応するテールオフセットの要素(または範囲外のときConstant[nil])に束縛されます。rhs_typeがType::Tuple以外の何かのとき、すべてのスロット(前方・rest・後方)はMUSTType::Combinator.untyped(Dynamic[Top])に束縛されます。スライスは、よりリッチなキャリアレシーバー格子が着地するまで、動的アリティの右辺について意図的に保守的なままです。- ネストされた
Prism::MultiTargetNodeターゲットはMUSTスロットの型を新しい右辺としてリカースします。これにより、両方のネストレベルがタプル形状のときa, (b, c) = [1, [2, 3]]はa -> 1・b -> 2・c -> 3を束縛します。 - 非ローカルターゲット(
InstanceVariableTargetNode・ConstantTargetNode・IndexTargetNode・CallTargetNode・ConstantPathTargetNode・ImplicitRestNode・式のない無名splat…)はMUST静かにスキップされます。これらはStatementEvaluatorがスレッドするローカル変数スコープに観測可能な寄与を持ちません。 - バインダーは整形式のPrism入力でMUST NOTraiseせず、その引数をMUST NOT変更しません。
ブロック引数束縛(Slice 6 phase C sub-phases 1 and 2)
Section titled “ブロック引数束縛(Slice 6 phase C sub-phases 1 and 2)”Rigor::Inference::BlockParameterBinderは、Prism::BlockNodeのエントリースコープ拡張を構築する標準サーフェスです。MUST以下を満たします。
BlockParameterBinder.new(expected_param_types:)は、受信側メソッドのRBSシグネチャから提供される位置ブロック引数1つにつき1つのRigor::Type値の順序付き配列を受け付けます。バインダーがこの配列から埋められないインデックス(配列が引数リストより短いか、スロットが配列で駆動されない種類)はMUSTデフォルトでDynamic[Top]です。bind(block_node)はMUST、引数名を束縛型へマップする宣言順の順序付きHash{Symbol => Rigor::Type}を返します。無名引数はMUST静かにスキップされます。Prism::MultiTargetNode分解スロット(|(a, b), c|形式)はMUSTスロットの期待型に対してRigor::Inference::MultiTargetBinderを通じて展開されます(Slice 6 phase C sub-phase 2)。すなわちType::Tupleスロットは要素ごとに分解されます。他の任意のキャリアはすべての内部ローカルをDynamic[Top]に縮退します。内部ターゲットはPrism::RequiredParameterNodeインスタンス(ブロック側のエンコード)です。MultiTargetBinderは束縛目的のためにそれらをMUSTPrism::LocalVariableTargetNodeと統一的に扱います。*rest引数はMUSTNominal["Array", [Dynamic[Top]]]に束縛されます。**kw_rest引数はMUSTNominal["Hash", [Nominal["Symbol"], Dynamic[Top]]]に束縛されます。明示的な&block引数はMUSTNominal[Proc]に束縛されます。キーワード引数(requiredとoptional)はMUSTDynamic[Top]に束縛されます。Slice 6 phase C sub-phase 1は受信側メソッドのブロックキーワードシグネチャを内省しないからです。NumberedParametersNode(暗黙の_1形式)はMUSTnumbered_node.maximumまで:_1・:_2…の束縛を生成します。それぞれは明示的な引数で用いるのと同じ位置ごとのexpected_param_types:配列で駆動されます。配列の長さを超えるスロットはMUSTデフォルトでDynamic[Top]です。これはSlice 6 phase C sub-phase 2の契約です。前のスライスの「束縛なし」挙動は置き換えられます。- バインダーは整形式のPrismブロックノードでMUST NOTraiseせず、その入力をMUST NOT変更しません。
Rigor::Inference::MethodDispatcher.expected_block_param_types(receiver_type:, method_name:, arg_types:, environment:)は、バインダーのexpected_param_types:配列を供給する標準クエリです。MUST以下を満たします。
- 選択されたRBSオーバーロードで宣言された位置ブロック引数(
required_positionals+optional_positionals)の順序付きArray<Rigor::Type>をMUST返します。ブロック専用のキーワード引数とrest形式はMUST返り値配列から除外されます。バインダーがそれらのスロットを独立に扱います。 - ブロック付き呼び出し(
Array#each { ... })が誤ってブロックなしオーバーロード(Array#each() -> Enumerator)を通じて束縛しないよう、block_required: trueでOverloadSelector.selectを通じてオーバーロードをMUST選択します。 - ジェネリックブロック引数がレシーバーの
type_argsを通じて解決されるよう、RbsDispatch.try_dispatchが用いるのと同じシェイプ/ジェネリック置換機構をMUST参照します(Tuple[Constant[1], Constant[2]]レシーバーはArray#eachのElemブロック引数をConstant[1] | Constant[2]に解決させます)。 - 環境・RBSローダー・レシーバーディスクリプタ・メソッド定義・選択されたオーバーロード・ブロック節が欠落または型なしのとき、MUST空配列を返します。バインダーはMUST空配列を「情報なし」として扱い、すべての引数を
Dynamic[Top]にデフォルトします。 Unionレシーバーについて、メンバーごとの答えを取り、すべてのメンバーが構造的に等しいブロック引数リストを生成するときのみMUST返します。それ以外ではMUST空配列を返します。したがって混合アリティのunionは、非決定的なブロック束縛を生成するのではなくDynamic[Top]に劣化します。- 整形式の入力でMUST NOTraiseしません。プローブをフェイルソフトに保つため、RBSの
DefinitionBuilder周りの防御的なrescue StandardErrorは許可されます。
Rigor::Inference::StatementEvaluatorは両方のサーフェスをMUSTPrism::CallNodeハンドラを通じて消費します。
- ハンドラは呼び出し式を純粋な値としてMUST評価し(既存のディスパッチチェーンとシェイプティアが依然として適用されるよう
Scope#type_ofに委ねる)、レシーバースコープをMUST変更せずに返します。ブロック効果は後置呼び出しスコープにMUST NOT漏れません。ブロック内のみで束縛されたローカルは、呼び出しの外側でMUST NOT観測可能です。 node.blockがPrism::BlockNodeのとき、ハンドラはレシーバーの外側スコープをBlockParameterBinder.bindが生成した束縛で拡張することでブロックのエントリースコープをMUST構築します。ブロック本体は外側スコープのローカルを継承し(Rubyのレキシカルスコーピング規則)、ブロック引数がその上に重ねられます。- ハンドラは、ノードごとのスコープインデックスが引数束縛を見るよう、
BlockNodeをMUSTsub_evalし、ブロックのエントリースコープを本体を通じてスレッドします。ブロックのPrism::BlockNode自体は、本体の文チェーンが拡張されたスコープ下で訪れられるよう、MUSTsub_eval(body, scope)に委ねるハンドラを持ちます。 - ハンドラの例外エンベロープはプローブの失敗を「期待型なし」としてMUST扱います。バインダーは依然として実行され、すべての引数を
Dynamic[Top]にデフォルトします。 - ブロック対応の結果型付け(Slice 6 phase C sub-phase 2)は
Rigor::Inference::ExpressionTyper#call_type_forにあります。すなわち呼び出しがPrism::BlockNodeを運ぶとき、型付け器はMUST同じブロックエントリースコープ(外側スコープ + BlockParameterBinder.bind)を構築し、そのスコープ下でブロックの本体を型付けし、本体の値型をblock_type:としてMethodDispatcher.dispatchに渡します。これがStatementEvaluatorをループに含めることを要求せずに、任意の呼び出し位置(CLItype-of・ScopeIndexer・素のScope#type_of)から[1, 2, 3].map { |n| n.to_s }をArray[String]に解決させる正確なメカニズムです。StatementEvaluatorのCallNodeハンドラはMUST整合を保ちます。すなわち結果型については同じScope#type_ofに委ね、ブロック本体をノードごとのスコープインデックスのためにのみ再評価します。
Slice 3 phase 2は任意の式の内部を通じてスコープをスレッドしません。すなわちfoo(x = 1)や[1, x = 2]はxを後置スコープに伝播しません。再帰下降は評価器のカタログがカバーしない式レベルの子で止まるからです。これは意図的なPhase 2の簡略化です。後のスライスはカタログを拡張して呼び出し引数や配列/ハッシュ要素を通じてスコープをスレッドするかもしれません。トップレベルの文的構成(代入・if・case・begin・ループ・括弧)は上記の指定どおりに伝播します。
Slice 3 phase 2は引数束縛の表現力も狭く制限します。バインダーは各スロットでオーバーロードにわたるunionを選びますが、MethodDispatcher::OverloadSelectorが戻り値型に対して行うような、引数呼び出し位置の型による束縛の精緻化はまだ行いません。RBSインターフェイス型(int・_ToS…)とエイリアスは既存のRbsTypeTranslator翻訳器を通じて依然としてDynamic[Top]に劣化するため、唯一のオーバーロードの引数がintであるdef first(n)の再定義はMUST観測可能にnをDynamic[Top]に束縛します。これは既存の翻訳器の漸進的型付け姿勢と一致します。
ナローイング(Slice 6 phases 1 and 2)
Section titled “ナローイング(Slice 6 phases 1 and 2)”Slice 6 phase 1はエンジンに最初のエッジ対応精緻化サーフェスを追加します。これはRigor::Inference::Narrowingを通じて公開され、Rigor::Inference::StatementEvaluatorがPrism::IfNode/Prism::UnlessNodeのthenとelseスコープ(およびPrism::AndNode/Prism::OrNodeのRHSエントリースコープ)を精緻化するために消費する純粋なモジュールです。Slice 6 phase 2はカタログをクラスメンバーシップ述語(is_a?・kind_of?・instance_of?)と信頼できる等価/不等価述語で拡張しつつ、同じ消費契約を保ちます。
型レベルナローイングプリミティブ
Section titled “型レベルナローイングプリミティブ”モジュールはMUST以下のモジュール関数を公開し、それぞれが新鮮なRigor::Type値を生成しその入力を決して変更しません。
Narrowing.narrow_truthy(type)—typeの真値フラグメント。Constant[v]はvがnilまたはfalseのときBotに縮退し、それ以外では保たれます。Nominal[NilClass]/Nominal[FalseClass]はBotに縮退し、他のNominalキャリアは保たれます。Unionは要素ごとに再帰します。Top・Dynamic[T]・Bot・Singleton・Tuple・HashShapeは変更されずに通過します。Narrowing.narrow_falsey(type)—typeの偽値フラグメント。Constant[nil]/Constant[false]とNominal[NilClass]/Nominal[FalseClass]は保たれます。他のConstant/NominalキャリアはBotに縮退します。Unionは要素ごとに再帰します。Singleton/Tuple/HashShapeはBotに縮退します(その住人は真値だからです)。Top/Dynamic[T]/Botは、解析器が偽値側を空と証明できないため、変更されずに通過します。Narrowing.narrow_nil(type)—typeのnilフラグメント。Constant[nil]とNominal[NilClass]は保たれます。非nilなConstant/NominalキャリアはBotに縮退します。Unionは要素ごとに再帰します。Top/Dynamic[T]は、下流ディスパッチがNilClassを通じて解決するよう、MUST標準的なConstant[nil]に絞り込みます。nilを決して住まないキャリア(Singleton・Tuple・HashShape)はBotに縮退します。Botはそれ自身のnilフラグメントです。Narrowing.narrow_non_nil(type)—typeの非nilフラグメント。narrow_nilのミラーです。すなわちnil専用キャリアはBotに縮退し、非nilキャリアは保たれます。Top/Dynamic[T]/Singleton/Tuple/HashShapeは変更されずに通過します。Unionは要素ごとに再帰します。Narrowing.narrow_equal(type, literal)(Slice 6 phase 2 sub-phase 2) — 信頼できるType::Constantリテラルに対するtypeの等価フラグメント。String・Symbol・Integerリテラルは、typeがすでに有限な信頼できるリテラルドメイン(Constantまたは信頼できるconstantsのUnion)であるときにのみMUST絞り込みます。Nil・true・falseはシングルトン値で、Integer | nilのような混合ドメインからMAY抽出できますが、Dynamic[Top]を比較されるリテラルにMUST NOT絞り込みません。FloatリテラルはMUST NOT絞り込みません。信頼できない入力はtypeを変更せずに返します。Narrowing.narrow_not_equal(type, literal)(Slice 6 phase 2 sub-phase 2) —narrow_equalのドメイン相対補集合。有限な信頼できるリテラルドメインから比較されるリテラルを除去し、混合ドメインからnil/true/falseのシングルトンメンバーが存在するときに除去します。StringやDynamic[Top]のような広いドメインに対して、無境界の差集合型をMUST NOT作成しません。Narrowing.narrow_class(type, class_name, exact: false, environment: Environment.default)(Slice 6 phase 2 sub-phase 1) —typeのクラスメンバーシップフラグメント。class_name引数はソースに現れるとおりの問われたクラスの修飾名("Integer"・"Foo::Bar")です。exact: falseはis_a?/kind_of?(サブクラス包含)をモデル化し、exact: trueはinstance_of?(厳密)をモデル化します。キャリア規則はMUST以下のとおりです。Constant[v]—v.classが述語を満たす(exact: falseでは問われたクラスのサブクラスまたは等しい、exact: trueでは等しい)とき保たれます。それ以外ではBotに縮退します。Nominal[C]— 束縛されたCが問われたクラスと同じ(またはexact: false下ですでにそのサブクラス)のとき保たれます。問われたクラスがexact: false下でCのサブクラスのとき、型はNominal[asked]に下方へ絞り込まれます(例えばis_a?(Integer)下のNominal[Numeric]はNominal[Integer]になります)。exact: false下の互いに素な階層とexact: true下の任意の非等価クラスはBotに縮退します。いずれかのクラス名が提供された解析器Environmentを通じて解決しないとき、結果はMUST保守的なまま残り、入力型を保ちます。Union— 要素ごとに再帰し、互いに素なメンバーを落とします。Tuple[*]/HashShape{*}— 問われたクラスに対して"Array"/"Hash"を通じて統一的に射影されます。Singleton[C]— 問われたクラスに対して"Class"を通じて統一的に射影されます(住人はクラスオブジェクトです)。Top/Dynamic[T]— 下流ディスパッチが問われたクラスを通じて解決できるよう、Nominal[asked]に絞り込みます。Bot— 保たれます。
Narrowing.narrow_not_class(type, class_name, exact: false, environment: Environment.default)(Slice 6 phase 2 sub-phase 1) — 同じ述語の偽値フラグメント。述語を満たすConstant/Nominal/Union/Tuple/HashShape/SingletonキャリアはBotに縮退します。満たさないキャリアは変更されずに保たれます(Nominalについて、解析器がよりリッチなキャリアなしに分離を証明できないため、!is_a?(Integer)下のNominal[Numeric]はNominal[Numeric]のままになります)。Top/Dynamic[T]/Botは変更されずに通過します。
これらのプリミティブはMUST決定的かつ構造的に純粋です。すなわち構造的に等しい入力での2つの呼び出しは構造的に等しい出力を生成し、それらを呼び出しても入力を決して変更しません。
述語レベルナローイング
Section titled “述語レベルナローイング”Narrowing.predicate_scopes(node, scope)はMUST[truthy_scope, falsey_scope]ペアを返します。nodeがnilまたはその形状が認識カタログにないとき、ペアはMUST[scope, scope]であり、これにより呼び出し元は特別な戻り値なしに「ナローイングなし」を観測します。認識されるSlice 6 phase 1のカタログは以下のとおりです。
Prism::ParenthesesNode— 存在するときbodyへ再帰します。Prism::StatementsNode— 最後の文を解析します。先行する文はMAYスコープ効果を持ちますが、StatementEvaluatorはNarrowingを呼ぶ前にすでに後置述語スコープを生成しているため、解析器は追加の効果をスレッドしません。Prism::LocalVariableReadNode— ローカルがscopeに束縛されているとき、真値 →narrow_truthy(local)、偽値 →narrow_falsey(local)に絞り込みます。束縛されていないローカルはナローイングなしフォールバックへフォールスルーします。Prism::CallNode— カタログは4つの述語形状をカバーし、呼び出しがブロックを持つかレシーバーがnilのときはすべて静かに拒否されます。recv.nil?(位置/キーワード引数なし):narrow_nil/narrow_non_nilを通じてレシーバーローカルを絞り込みます。- 単項
!recv(name == :!、位置/キーワード引数なし):recvを解析して結果のペアをスワップします。 recv.is_a?(C)/recv.kind_of?(C)/recv.instance_of?(C)(Slice 6 phase 2 sub-phase 1): レシーバーがPrism::LocalVariableReadNodeであり、単一引数が静的な定数参照(Prism::ConstantReadNodeまたはPrism::ConstantPathNode)であることを要求します。修飾名はMUST親ウォークFoo::Bar形式を介してレンダリングされます。真値エッジはローカルをnarrow_class(local, class_name, exact:, environment: scope.environment)を通じて再束縛します(exact: trueはinstance_of?のみ)。偽値エッジはnarrow_not_class(local, class_name, exact:, environment: scope.environment)を通じて再束縛します。それ以外(ローカル専用引数・メソッド呼び出し引数・複数引数)はMUSTナローイングなし分岐へフォールスルーします。local == literal/literal == localと!=ミラー(Slice 6 phase 2 sub-phase 2): 一方の側がPrism::LocalVariableReadNode、もう一方の側が静的な信頼できるリテラル(String・Symbol・Integer・true・falseまたはnil。Floatではない)であることを要求します。==の真値エッジはローカルをnarrow_equalを通じて再束縛します。偽値エッジはnarrow_not_equalを通じて再束縛します。!=はそれらのエッジをスワップします。認識される各等価述語はMUSTさらにFactStore::Factを取り付けます。すなわちローカル型が変わったときはlocal_bindingバケットを使い、それ以外では広いまたは動的なドメインが不健全な肯定的ナローイングなしに関係を保持するようrelationalバケットを使います。
- RBS::Extended述語注釈を伴う
recv.foo(arg)(Slice 7 phase 15): 受信側メソッドのRBSシグネチャがそのRBS::Definition::Method上に%a{rigor:v1:predicate-if-true <target> is <Class>}または%a{rigor:v1:predicate-if-false <target> is <Class>}注釈を運ぶとき、解析器はマッチする束縛を対応するエッジでnarrow_classを通じてMUST絞り込みます。パーサは厳密な形状を認識します。すなわち<target>はRubyの引数名(選択されたオーバーロードのrequired_positionals/optional_positionalsに対してマッチ)またはselfです。<Class>は単一のオプショナルに名前空間付けされたクラス識別子(String・Foo::Bar・::Foo)です。引数ターゲットについて、ナローイングはマッチする呼び出し引数がPrism::LocalVariableReadNodeのときにのみ適用されます。selfターゲットについて、4つのレシーバー形状が参加します(v0.1.1 Track 1 slice 3)。すなわちPrism::LocalVariableReadNodeとPrism::InstanceVariableReadNodeはScope#with_local/with_ivarを通じて再束縛し、暗黙self(nilレシーバー)とPrism::SelfNodeの両方はScope#with_self_typeを通じて再束縛します。暗黙self経路はInference::Narrowing#analyse_callがnilレシーバー形状をRBS::Extended経路まで通すことを要求します。resolve_rbs_extended_methodはレシーバーがnilのときscope.type_of(node.receiver)の代わりにscope.self_typeを参照します。他のレシーバー形状(メソッドチェーン・式)は依然としてフォールスルーします。 recv === local(Slice 7 phase 4): case等価述語は3つのレシーバー形状について認識されます。- クラス/モジュールレシーバー(静的な
Prism::ConstantReadNodeまたはPrism::ConstantPathNode) —local.is_a?(receiver)と同型です。真値と偽値のエッジはis_a?/kind_of?と同一にclass_predicate_scopesを通じて生成されます。 - Rangeリテラルレシーバー(
Prism::RangeNode、オプショナルにPrism::ParenthesesNodeにラップされる) — 真値エッジでlocalを整数/浮動小数端点ではNumericに、文字列端点ではStringに絞り込みます。Range#===は強制不可能な型と範囲内失敗の両方でfalseを返すため、偽値エッジはエントリー型を保ちます。 - Regexpリテラルレシーバー(
Prism::RegularExpressionNode/Prism::InterpolatedRegularExpressionNode) — 真値エッジでlocalをStringに絞り込みます。Rangeと同じ偽値規則です。 それ以外(動的レシーバー・カスタム===メソッド・非ローカルLHS)はMUSTナローイングなし分岐へフォールスルーします(両エッジでエントリースコープを保ちます)。
- クラス/モジュールレシーバー(静的な
Prism::AndNode—a && bは真値エッジをaの真値スコープ下のbの真値スコープで絞り込みます。偽値エッジはScope#joinを介してaの偽値スコープ(bがスキップ)とbの偽値スコープ(bが実行されたが偽値を返した)をunionします。Prism::OrNode—a || bは真値エッジをaの真値スコープと(aの偽値スコープ下の)bの真値スコープのScope#joinで絞り込みます。偽値エッジはaの偽値スコープ下のbの偽値スコープです。
解析器は認識されない述語形状でMUST NOTraiseせず、いかなるトレーサーをもMUST NOTスレッドせず(述語解析はすでに型付けされたスコープ情報を参照する純粋なクエリです)、レシーバースコープをMUST NOT変更しません。
StatementEvaluatorとの統合
Section titled “StatementEvaluatorとの統合”Rigor::Inference::StatementEvaluatorはMUST以下のように述語解析器を消費します。
Prism::IfNode— 述語を評価してpost_predを得たのち、Narrowing.predicate_scopes(node.predicate, post_pred)を呼び出して(truthy_scope, falsey_scope)を導出します。then分岐はMUSTtruthy_scope下で評価されます。else分岐(または不在のelse、これはConstant[nil]と述語の後置スコープを寄与します)はMUSTfalsey_scope下で評価されます。分岐型はunionされ、後置スコープは既存のnil注入規則を通じてマージされます。Prism::UnlessNode—IfNodeと同じ形状ですが、then分岐(述語が偽値のときに実行される)はMUSTfalsey_scope下、else分岐はMUSTtruthy_scope下で評価されます。Prism::AndNode/Prism::OrNode— LHSを評価してleft_scopeを得たのち、Narrowing.predicate_scopes(node.left, left_scope)を呼び出します。RHSはMUST&&ではLHSの真値スコープ下、||ではLHSの偽値スコープ下で評価されます。後置スコープはMUST依然としてleft_scopeとRHSの後置スコープのnil注入付き結合(「RHSはときどき実行された」をモデル化)であり、これによりRHSからの半束縛の名前が引き続きnil注入します。結果型はMUST&&ではunion(narrow_falsey(left_type), right_type)、||ではunion(narrow_truthy(left_type), right_type)です。
Slice 6 phase 1 + phase 2は、真偽値性・nil?・クラスメンバーシップ述語・信頼できる等価/不等価述語についてローカル変数ナローイングを束縛します。以下のサーフェスは意図的に後続に委ねられます。
- 等価ナローイングは意図的に狭いです。すなわちユーザー定義の等価・
eql?・===・Floatリテラル・広いString/Symbol/Integerドメイン・Dynamic[Top]を精密なリテラルドメインに昇格しません。それらの比較は関係的ファクトをMAY運びますが、値ナローイングはRBS/プラグイン宣言の効果を待ちます。 - クロージャに捕捉されたローカルはナローイング時には通常のローカルとして扱われます。Slice 6 phase C sub-phase 3aは
Rigor::Inference::ClosureEscapeAnalyzer.classify(receiver_type:, method_name:, environment:)を導入します。これはブロック受け入れ呼び出しに対して:non_escaping・:escaping・:unknownを返す純粋なクエリです。Sub-phase 3aはRBSブラインドです。すなわち解析器はコア反復メソッド(Array/Hash/Range/Set/Enumerator/Enumerator::LazyのEnumerableメソッド、Hash#each_pair/each_key/each_value/transform_keys/transform_values、Range#step、Integer#times/upto/downto、Object#tap/then/yield_self)を:non_escapingとしてカバーし、既知のリテイナー(Module#define_method・Class#define_method・Thread.new/start/fork・Fiber.new・Proc.new)の小さなセットを:escapingとしてカバーするハードコードされたカタログを出荷します。TupleレシーバーはArrayに、HashShapeはHashに射影され、Constant[v]は値のクラスを通じて射影されます。Top・Dynamic[T]・Union、およびカタログ化されていない任意の(class, method)ペアは:unknownを返します。解析器はスコープをMUST NOT変更せず、認識されない入力でMUST NOTraiseしません。消費者はファクト保持のためにMUST:unknownを:escapingと同じ程度に保守的に扱います。Sub-phase 3aで解析器は純粋なクエリです。 - Slice 6 phase C sub-phase 3bは
ClosureEscapeAnalyzerをStatementEvaluator#eval_callに配線します。Prism::CallNodeがPrism::BlockNodeを運び、解析器が呼び出しを:escapingまたは:unknownに分類するとき、評価器はMUST後置呼び出しスコープにbucket: :dynamic_origin・predicate: :closure_escape・target: Target.new(kind: :closure, name: <method symbol>)・payload: { method_name:, classification: }・stability: :unstableのFactStore::Factを取り付けます。:non_escaping分類(または任意のブロックなし呼び出し)はMUST後置呼び出しのScope#fact_storeを変更せずに残します。Sub-phase 3cはさらに、ブロック本体が再束縛できる各捕捉外側ローカルの絞り込まれた型を落とし、Scope#with_localを通じてDynamic[Top]に置き換えます(これはローカルのlocal_bindingファクトも無効化します)。「捕捉外側ローカル」セットは、名前が(a)呼び出し位置スコープのScope#localsに現れ、(b)ブロック自身(BlockParameterBinder.bindからのブロック引数、加えてBlockParametersNode#locals上の;プレフィックス付きブロックローカル)によって導入されていないPrism::LocalVariableWriteNodeを求めてBlockNode#bodyを歩いて計算されます。ブロックが読むだけのローカルはMUST絞り込まれたまま残ります。ブロック引数によってシャドウされた名前への書き込みはMUST NOTカウントされません。保守的な落としは型をDynamic[Top]に置き換えます。将来のsub-phaseは、ブロック本体効果追跡が利用可能になり次第、これをブロックの実際の書き込みのunionにMAY精緻化できます。 - インスタンス・クラス・グローバル変数ナローイングはスコープ外のままです。今日解析器によって認識されるのは
Prism::LocalVariableReadNodeのみです。(Slice 7 phase 1はScope#with_ivar/#with_cvar/#with_globalを通じてivar/cvar/グローバル書き込みの型を束縛しますが、述語ナローイングカタログは依然としてLocalVariableReadNodeレシーバーについてのみ発火します。)
これらの境界は、Slice 6 phase 1の呼び出し元をMUST NOT静かに劣化させません。すなわち認識カタログ外の述語はMUST両エッジでエントリースコープを観測し、Slice 3 phase 2の挙動を保ちます。
環境サーフェス
Section titled “環境サーフェス”Rigor::Environmentは現在のスコープ外の型宇宙に対するエンジンのビューです。すなわちnominalクラス・RBS定義・プラグイン提供のファクト(Slice 6以降)・任意の他のモジュールレベル情報です。Slice 1が束縛する最小の公開サーフェスは以下のとおりです。
Rigor::Environment#class_registry— RubyのClassまたはModuleオブジェクトをRigor::Type::Nominalに解決できるRigor::Environment::ClassRegistryを返します。Rigor::Environment::ClassRegistry#nominal_for(class_object)— 登録されたクラスについて登録済みのRigor::Type::Nominalを返すか、クラスが登録されていなければraiseします。Rigor::Environment::ClassRegistry#registered?(class_object)— クラスが登録されているかどうかについてtrueまたはfalseを返します。Rigor::Environment::ClassRegistry#nominal_for_name(name)— Symbol/Stringのクラス名について登録済みのRigor::Type::Nominalを返すか、名前が未知のときnilを返します。
Slice 4は以下を導入します。
Rigor::Environment#rbs_loader— 環境に取り付けられたRigor::Environment::RbsLoaderを返すか、「RBSブラインド」環境(テストフィクスチャ)についてnilを返します。Rigor::Environment::RbsLoader#class_known?(name)— RBS環境がその名前のクラスまたはモジュールを定義しているときtrueを返します。nil安全でstring/symbol寛容です。Rigor::Environment#class_ordering(lhs, rhs)— Slice 6 phase 2 sub-phase 2。まず静的レジストリを参照し次にRBSローダーを参照することで:equal・:subclass・:superclass・:disjoint・:unknownを返します。これはクラスメンバーシップナローワーの階層オラクルです。述語位置でアドホックなObject.const_getにMUST NOT依存しません。Rigor::Environment::RbsLoader#class_ordering(lhs, rhs)— 2つの名前についてRBS::Definition#ancestorsを比較することで同じ順序語彙を返します。未知または不正な名前は:unknownを返します。Rigor::Environment::RbsLoader#instance_method(class_name:, method_name:)— 与えられたインスタンスメソッドの解決されたRBS::Definition::Methodを返すか、クラスまたはメソッドが未知のときnilを返します。継承されたメソッドはMUSTこの呼び出しを通じて見えます(ローダーは祖先チェーンを歩くRBS::DefinitionBuilder#build_instanceを使います)。Rigor::Environment::RbsLoader#singleton_method(class_name:, method_name:)— Slice 4 phase 2b。与えられたクラスメソッドの解決されたRBS::Definition::Methodを返すか、クラスまたはメソッドが未知のときnilを返します。継承されたクラスメソッドはMUST見えます(例:Class#new・Module#name)。ローダーはRBS::DefinitionBuilder#build_singletonを使います。インスタンスとシングルトンの名前空間はMUST互いに素です — 例えばModule#instance_methodsはシングルトン側で解決され、インスタンス側では静かに不在です。Rigor::Environment::RbsLoader.new(libraries:, signature_paths:)— Slice 4 phase 2a。呼び出し元がローダーをRBSコアを越えて拡張できるようにします。librariesはRBS::EnvironmentLoader#add(library:, version:)が受け付けるstdlibライブラリ名の配列です。signature_pathsは追加の.rbsファイルのPathnameまたはStringディレクトリの配列です。未知のライブラリ名はMUSTフェイルソフトです(ローダーはRBS::EnvironmentLoader#has_library?を介してそれらをスキップします)。存在しないシグネチャパスはMUSTビルド時に静かに落とされます。RbsLoader#librariesと#signature_pathsは、ラウンドトリップと可観測性のために設定された値を公開します。Rigor::Environment#nominal_for_name(name)— まずクラスレジストリを参照し、それから(存在するとき)RBSローダーを参照します。最初のヒットのRigor::Type::Nominalを返すか、ヒットなしのときnilを返します。これは「クラスnameのインスタンス」のための構築ヘルパーです。Rigor::Environment#singleton_for_name(name)— Slice 4 phase 2b。定数のクラスオブジェクトに対するRigor::Type::Singletonを返すか、レジストリまたはRBSローダーのいずれにもnameのクラスが知られていないときnilを返します。これはPrism::ConstantReadNode/Prism::ConstantPathNodeを型付けするための標準エントリーポイントです。ExpressionTyperは、結果がインスタンス型ではなくクラスオブジェクトの型になるよう、MUSTこれを通じてルーティングします。Rigor::Environment#class_known?(name)— Slice 4 phase 2b。レジストリまたはRBSローダーがnameを知っているときtrueを返す便利な述語です。キャリアを実体化せずに存在チェックが必要な呼び出し元に有用です。Rigor::Environment.for_project(root:, libraries:, signature_paths:)— Slice 4 phase 2a。プロジェクト対応のEnvironmentを構築するファクトリーです。signature_pathsがnilでディレクトリが存在するとき、<root>/sigをデフォルトのシグネチャパスとして自動検出します。それ以外では空リストを使います。呼び出し元は明示的なsignature_paths配列(無効化のための[]を含む)を渡してMAY自動検出をオーバーライドできます。プロジェクト対応のローダーは呼び出し元の作業ディレクトリと設定に依存するため、ファクトリーはMUST新鮮なEnvironmentインスタンスを返します —Environment.defaultとMUST NOTメモ化または共有しません。Slice Aのstdlib拡張: ファクトリーはMUSTEnvironment::DEFAULT_LIBRARIES(厳選されたstdlibセット:pathname・optparse・json・yaml・fileutils・tempfile・uri・logger・date、加えて解析器隣接のprismとrbsgem)を呼び出し元のlibraries:引数の前にマージし、順序を保ちながら結果を重複排除します。未知のライブラリはMUSTフェイルソフトのまま残ります。RbsLoader#build_envはすでにRBS::EnvironmentLoader#has_library?を通じてフィルタしています。
0.1.xサイクルは以下の読み取り側アクセサを追加しました(すべてEnvironment.new / for_projectのキーワード引数経由でシードされattr_readerとして公開されます;対応する事前パスを省略した環境が以前と同じく振る舞うよう、それぞれは空 / nil安全な値をデフォルトとします):
Rigor::Environment#project_patched_methods— プロジェクトの.rigor.ymlpre_eval:リストから投入されるRigor::Inference::ProjectPatchedMethodsレジストリ(ADR-17)。ディスパッチャーのプロジェクトパッチメソッドティアが参照する;デフォルトはProjectPatchedMethods::EMPTY。レジストリの読み取りサーフェスは#lookup(class_name:, method_name:, kind:)と#empty?。Rigor::Environment#dependency_source_index— オプトインのgemソースカタログ(ADR-10)、依存ソース推論ティアが参照する。Rigor::Environment#synthetic_method_index— ADR-16 / ADR-18基板のemitテーブル、合成メソッドティアが参照する。Rigor::Environment#plugin_registry— 実行のためにロードされたRigor::Plugin::Registry(RBSローダーへマージされるプラグイン貢献のsignature_paths:(ADR-25)、およびCheckRulesとバインダーが参照するopen_receivers:/protocol_contracts:のソースでもある)。Rigor::Environment#hkt_registry— 軽量HKT型関数レジストリ(ADR-20)、HKT組み込み戻り値ディスパッチャーティアが参照する。これはMUSTこの順序でのマージ(最後の書き込みが勝つ)です: バンドルされたBuiltins::HktBuiltins.registryベース、次にプラグインオーバーレイ(Plugin::Registry#hkt_overlay_registry、ロードされた各プラグインのhkt_registrations:/hkt_definitions:を登録順にflat-mapする)、その上に任意のユーザー.rbsオーバーレイ。ゲッターはメモ化され、何も貢献しないときはMUSTHktRegistry::EMPTYを返すので、レジストリのない実行はマージをスキップします。
Slice 6はファクトストアアクセスを導入します。後のスライスで追加されたメソッドはSlice 1のサーフェスをMUST NOT変更しません。
プラグイン貢献の環境入力(ADR-16ティアA / ADR-32)
Section titled “プラグイン貢献の環境入力(ADR-16ティアA / ADR-32)”2つのプラグイン宣言のマニフェストサーフェスがディスパッチャーティアの外でエンジンに消費されます;それらの値オブジェクトのシェイプ(shape)はplugin.md / macro-substrate.mdにあり、それらのエンジン消費はここで規範的です。
-
source_rbs_synthesizer:ファイルごと合成(ADR-32)。env構築時に、解析された各ソースファイル × ロードされた各プラグインのシンセサイザー呼び出し可能オブジェクトについて、Environment#collect_virtual_rbsがcallable.call(path)を呼び出します。返されたRBS Stringは仮想名virtual:<plugin id>:<path>の下で解析環境にマージされます;nilまたは空の返り値は何も貢献しません;[:error, message]タプルはraiseされる代わりに合成レポーターへルーティングされます(フェイルソフト)。各(file, plugin)呼び出しはCache::Storeを通じて、ファイルのコンテンツSHAをプラグインのPluginEntry(id + version + config_hash)と合成したキーでメモ化されるので、コンテンツや設定の変更がちょうどそのエントリーを無効化します。signature_paths:(静的なバンドルRBS)とは異なります: シンセサイザーは毎回の実行でプロジェクトソースからRBSを導出します。 -
block_as_methods:self型ナローイング(ADR-16ティアA)。ExpressionTyperがブロックを伴う呼び出しを型付けするとき、Inference::MacroBlockSelfType.narrow_self_type_for(scope:, call_node:, receiver_type:)を参照します。マッチ契約は狭いです: 呼び出しのレシーバー型はMUSTSingleton[X](クラスレベルのDSL呼び出し——クラス本体内の暗黙的self、または明示的なApp.get(...))であり、XはMUST登録されたエントリーのreceiver_constraintに等しいか継承する(Environment#class_ordering経由)でなければならず、呼び出しのメソッド名はMUSTそのエントリーのverbsにあります。マッチ時にはブロック本体がself_typeをインスタンス型Nominal[X]にナローイングして型付けされます(Sinatraのgenerate_methodに対応し、これはブロックをユーザーのアプリクラスのインスタンスメソッドに変えます);マッチしないときヘルパーはnilを返し、ブロックのselfは変わりません。ルックアップはフェイルソフトです(class_orderingの失敗は「マッチなし」に劣化します)。これはティアAの床です: ブロック内の裸の識別子メソッドルックアップは、その後エンジンの通常のself_type駆動の経路を通じて解決されます。
クラスレジストリはMUST常に以下のRubyクラスを認識します。すなわちInteger・Float・String・Symbol・NilClass・TrueClass・FalseClass・Object・BasicObjectです。実装は、列挙されたクラスが存在し続ける限り、このリストをMAY拡張できます。
デフォルトのRigor::Environment.defaultは、RBSコア(オプトインライブラリなし、シグネチャパスなし)をカバーするデフォルトのRbsLoaderをMUST取り付けます。Rigor::Environment.newを構築(kwargsなし)すると、テストフィクスチャがRBS起動コストを払わずにエンジンの挙動をアサートできるよう、MUST RBSブラインド環境を生成します。for_projectファクトリーは、本番CLIコマンドおよびローカルのsig/ツリーが必要な任意の他の呼び出し位置に対する標準エントリーポイントです。
決定性とキャッシュ
Section titled “決定性とキャッシュ”Scope#type_ofの結果は、与えられた(scope, node)ペアについてMUST決定的です。キャッシュはMAY使われ、同一性(equal?)または構造的等価性(==)でMAYキー付けできます。キャッシュは観測可能な挙動をMUST NOT変更し、パフォーマンスのみを変更します。
構造的に等しく比較される2つのスコープは、それらが同じRubyオブジェクトでなくても、同じノードに対してScope#type_ofからMUST構造的に等しい結果を生成します。
安定性とバージョニング
Section titled “安定性とバージョニング”本ドキュメントの契約はメジャーバージョン内で安定です。以下は加えて安定です。
Scope#type_ofの形状(入力型・戻り値型・純粋性・オプショナルなtracer:キーワード)。Scope.empty(environment:)コンストラクタシグネチャ。Scope#join(other)のセマンティクス。すなわち同一環境要件・束縛された名前の交差・型のunion。- フェイルソフトポリシーとその
Dynamic[Top]戻り値。 - フォールバックトレーサープロトコル(
record_fallback・events・empty?・size・each)とRigor::Inference::Fallback値オブジェクト。 - 上で列挙された最小のクラスレジストリサーフェス。
Rigor::AST::Nodeマーカーモジュールと、上記のラウンドトリップ挙動を持つRigor::AST::TypeNodeの存在。- メソッドディスパッチ境界。すなわちメソッド要約テーブルは
Rigor::TypeのインスタンスにMUST NOT存在しません。
以下は、後のスライスがそれらを昇格させるまで、安定性契約から明示的に外れます。
ExpressionTyperが認識するPrismノードの正確なカタログ。Rigor::Scopeの内部レイアウトとそのキャッシュ戦略。- ファクトストアスキーマ(Slice 6以降)とRBSローダーキャッシュ形状(Slice 4以降)。
- ADR-3の未決事項の解決に依存する、
Rigor::Typeのケイパビリティと射影サーフェス。
関連ドキュメント
Section titled “関連ドキュメント”docs/internal-spec/internal-type-api.md— 型付け器が消費する型オブジェクト公開契約。docs/internal-spec/implementation-expectations.md— 型付け器を取り巻くエンジンサーフェス契約(Scope結合・ファクトストア・効果モデル・ケイパビリティロール推論)。docs/adr/4-type-inference-engine.md— 設計根拠・スライスロードマップ・ADR-3の未決事項に対する暫定回答。docs/adr/3-type-representation.md— 型オブジェクト表現と、その暫定回答にADR-4がコミットしている未決事項。docs/adr/43-rbs-complete-ancestor-resolution.md— RBSバックのディスパッチティアがRBS完全祖先のRubyの部分型に対して行う、許可リスト方式の継承メソッド解決。docs/type-specification/relations-and-certainty.md— 部分型・漸進的一貫性・3値セマンティクス。docs/type-specification/value-lattice.md— フェイルソフト経路で用いるDynamic[T]代数。docs/type-specification/control-flow-analysis.md— Slice 6 narrowing target.
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.