ADR-1: 型モデルとRBSスーパーセット戦略
ステータス: Accepted;実装・出荷済み。
型モデルは解析器全体でライブである。何を解析器がするかについてはdocs/type-specification/とdocs/types.mdが、なぜそうするかについては本ADRが権威を持つ。
ADR-1は型モデルの設計決定とその根拠を記録します。コンパニオンドキュメントdocs/types.mdは型仕様です。すなわち正規化・ナローイング(narrowing)・消去・シグネチャ処理・診断サーフェス(surface)のレベルで解析器がどう振る舞うかを定義します。2つのドキュメントが同じ領域を議論するとき:
docs/types.mdは具体的な規則・デフォルト・予算を含む解析器の挙動について権威的です。- ADR-1はなぜ決定が取られたかと、フォローアップ作業のスコープを定める設計境界について権威的です。
- 2つのドキュメントが観測可能な挙動で食い違うときは、
docs/types.mdを束縛するテキストとして扱い、ADR-1を一致するよう更新します。ADR-1は仕様にある挙動契約(contract)を黙って言い直してはいけません。
ADR-1はまたプラグイン拡張API設計についてはADR-2に委ねます。ADR-1はADR-2が取り付けなければならない解析器側のサーフェス(Scopeクエリ・ファクト(fact)貢献・ケイパビリティロール・変異サマリー・診断識別子プレフィックス)のみを固定します。具体的なプラグインのライフサイクル・設定・マージ規則はADR-2で規範的です。
コンテキスト
Section titled “コンテキスト”Rigorは推論ファーストのRuby静的解析器です。既存のRBSエコシステムと相互運用しつつ、RBSが表現できるよりも精密な内部型をサポートしなければなりません。
RBSはすでにリッチな型構文を定義しており、これには名前的型(nominal type、公称型とも)・シングルトンクラス型・リテラル型・unions・交差・オプショナル・レコード・タプル・proc型・型変数・self・instance・class・bool・untyped・nil・top・bot・voidが含まれます。
RigorはまたPHPStan・TypeScript・Pythonのtyping仕様から積極的に学ぶべきです。それらのシステムは、実用的な静的解析がリテラル型・有限union・制御フローナローイング・否定的ファクト・シェイプ(shape)型・漸進的型付け規律・表現力のある型演算子の恩恵を受けることを示します。Rigorはそれらの構文を批判的検討なしにコピーするのではなく、RubyとRBSにそれらのアイデアを適応させるべきです。
初期設計要件は以下のとおりです。
- すべてのRBS型は有効なRigor型であり、RBS→Rigorの方向は無損失です。すなわち任意のRBS型はRigorの内部表現を通じて精度を失わずにラウンドトリップします。
Dynamic[T]のような内部ラッパーは境界で可逆です。 - RigorはRBSよりリッチな型を推論してかまいません。
- すべてのRigorで推論された型は有効なRBSに保守的に消去できますが、Rigor→RBSの方向は一般的に無損失ではありません。すなわち消去はリファインメント(refinement、篩型とも)・リテラルunion・シェイプ・動的起源のprovenanceを縮退させるかもしれません。消去はRigorが証明したより狭い型を決して生成してはなりませんが、より広い型を生成してかまいません。
untyped・top・bot・voidのような特別なRBS型は、アドホックなエイリアスとしてではなく、型理論的な明確さで処理されなければなりません。- RBSを超える型は、暫定的な
RBS::Extended規約の下でRBS注釈に記録されてかまいません。
互換性階層は以下のとおりです。
- RBSとrbs-inlineは型構文とインライン注釈互換性についての一次規範です。
- Steep 2.0の挙動は、散文仕様が挙動を開いておくときに既存の注釈がどう解釈されるかについての二次規範です。
- TypeScript・PHPStan・Pythonのtypingは、欠けた概念と実用的な解析器機能を見つけるために用いる設計参照です。それらは構文互換性ターゲットではありません。
このADRはRigorチェックアウト内の指示的参照パスとしてreferences/下の位置(例えばreferences/phpstan/...・references/typescript/...・references/python-typing/...)を引用します。正確なファイルと行番号はmake init-submodulesで設定されたサブモジュールリビジョンに依存します。特定のチェックアウトでパスが解決しない場合、引用を厳格な参照ではなく対応するリビジョンの上流リポジトリへのポインタとして扱ってください。
- 入力と出力についてRBS互換性を保ちます。
- アプリケーションコードをRigor固有のインライン型構文から自由に保ちます。Rigorは依然として既存のRBS・rbs-inline・Steep互換の注釈コメントを型ソースとして消費してかまいません。
- 精密な制御フローとデータフロー推論をサポートします。
- Rubyのセマンティクスに合うところでPHPStan・TypeScript・Pythonスタイルのナローイングをサポートします。
- 漸進的型付け境界を明示的にします。
- エクスポートされたRBSを保守的かつ説明可能にします。
- フレームワークの挙動をコアに焼き込まずに、プラグイン提供の型ファクトの余地を保ちます。
- Rigorは互換性のないシグネチャ言語を発明する必要はありません。
- Rigorは生成されたRBSにすべての内部リファインメントを公開する必要はありません。
- Rigorは基底セマンティクスを実装する前にすべての型演算子構文を確定する必要はありません。
- Rigorは最初のMVPで完全な最終型格子を実装する必要はありません。
検討された選択肢
Section titled “検討された選択肢”Option A: RBS型のみを使う
Section titled “Option A: RBS型のみを使う”RigorはRBSが綴ることができる型を正確に表現できるかもしれません。
利点:
- 単純なエクスポート経路。
- 既存ツールとの密接な整合。
- 小さな初期実装。
欠点:
- 推論はリテラルセット・整数境界・真偽値リファインメント・動的起源provenanceのような有用なファクトを失います。
- 診断はあまり精密でなくなります。
voidとuntypedは早期に広いエイリアスとして扱われがちです。- PHPStan・TypeScript・Pythonスタイルのリファインメントはうまく表現できません。
Option B: 保守的な消去を伴うRBSスーパーセットを使う
Section titled “Option B: 保守的な消去を伴うRBSスーパーセットを使う”Rigorはすべてのrbs型を表現し、内部専用のリファインメントを追加できます。エクスポートはそれらのリファインメントを保守的なRBSへ変換します。
利点:
- RBSを相互運用フォーマットとして保ちます。
- 精密な推論と診断を可能にします。
- 漸進的型付けと高度なリファインメントへの原則的経路を提供します。
- 肯定的・否定的ファクトを伴う制御フロー解析をサポートします。
- アプリケーションコード注釈なしの推論ファースト解析というプロジェクトゴールと一致します。
欠点:
- 実際の消去パスが必要です。
- 別個の正規化・部分型(subtype)・一貫性ロジックが必要です。
- エクスポートされたRBSがRigorの内部型より精密でないとき、ユーザーは説明が必要かもしれません。
- Rigor専用の型演算子の構文は注意深く設計されなければなりません。
Option C: RBSとRBS::Extended注釈のみを使う
Section titled “Option C: RBSとRBS::Extended注釈のみを使う”Rigorは独立した内部型モデルを避け、すべての拡張をRBS注釈として表現できるかもしれません。
利点:
- すべての明示的な型メタデータをRBS宣言・メンバー・オーバーロードに取り付けたまま保ちます。
- 標準のRBSパーサに対して見えないままです。
- 高度なライブラリシグネチャへの移行経路を提供します。
欠点:
- CFAが生成する推論されたファクトには注釈は十分ではありません。
- 注釈を構造化されない第2言語に変えるリスクがあります。
- 内部正規化・部分型・消去を解決しません。
Option D: 別個のRigorシグネチャ言語を作る
Section titled “Option D: 別個のRigorシグネチャ言語を作る”Rigorは新しい完全なシグネチャ言語を定義し、オプショナルにRBSを生成できるかもしれません。
利点:
- 最大の表現力。
- 内部概念をRBSの制約に合わせる必要がない。
欠点:
- エコシステムを分裂させます。
- 学習と保守コストを追加します。
- 依存関係に既存のRBS型を使うというゴールと衝突します。
- Rigorが意図的に避けている注釈ワークフローを助長します。
Working Decision
Section titled “Working Decision”Option Bを、Option Cの制約された一部とともに採用します。すなわちRigorの型言語は保守的なRBS消去を伴うRBSの厳格なスーパーセットであり、RBS::Extended注釈は*.rbsファイルにRigor専用のファクトを記述してかまいません。
RBSは境界フォーマットのままです。Rigorの内部型表現はRBSが表現できないリファインメントを含んでかまいませんが、それらのリファインメントは常に有効なRBS消去を持たなければなりません。
RBS::Extended注釈は通常のRBSの上に重ねられるメタデータです。それは内部推論の代替ではなく、Rubyのアプリケーションコード内で注釈を要求するべきではありません。
Rigorが自身のシグネチャ(組み込みカタログエントリー・推論されたユーザーメソッド型・RBS::Extendedペイロード)を書くときに候補型から選ぶ方法は、ADR-5の非対称ロバストネス原則によって統治されます。すなわち下流の精度伝播を最大化するための厳格な戻り値、呼び出し元に防御的な強制を貼り付けるよう強いるのを避けるための寛容な引数です。手書きのRBSオーサリングが束縛します。原則はRigor自身がシグネチャを書くときのデフォルト選択を導きます。
キー設計ポイント
Section titled “キー設計ポイント”部分型と漸進的一貫性は分離されている
Section titled “部分型と漸進的一貫性は分離されている”Rigorは通常の部分型と漸進的一貫性を区別すべきです。
topは最大の静的値型です。botは空型です。untypedは動的型であり、RBSが漸進的型付けのためにそれをすべての型の部分型かつ上位型と記述していても、topに縮退させてはいけません。
この分離により、漸進的コードが型検査されることを依然として許しつつ、Rigorはチェックされていない境界を追跡できます。
内部的には、動的起源の値はDynamic[T]として表現されるべきで、ここでTは現在知られている静的ファセットです。素のuntypedはDynamic[top]です。これはユーザー向けのRBS構文ではありません。これは、値が漸進的境界から来たという事実を失うことなく、Rigorがチェックされていない値を絞り込めるようにする実装装置です。
動的代数は、動的起源マーカーを保ちつつ、通常の型演算子を通じて静的ファセットを保ちます。
Dynamic[A] | Dynamic[B] = Dynamic[A | B]T | Dynamic[U] = Dynamic[T | U]Dynamic[T] & U = Dynamic[T & U]Dynamic[T] - U = Dynamic[T - U]
ジェネリック位置は動的起源スロットを保ちます。Array[untyped]はArray[Dynamic[top]]になるため、要素の読み込み・書き込み・漏れを精密に説明できます。部分型とメンバールックアップは利用可能なときに依然として静的ファセットを使います。漸進的一貫性は動的境界でのみ適用されます。
厳格モードはコア関係を変えるのではなく、このprovenanceを使います。あるレベルは動的から精密への境界横断とチェックされないジェネリック漏れを報告するかもしれません。より厳格なレベルは、証明が動的起源ファクトに依存する操作も報告するかもしれません。
ドキュメンテーションは、~Tが否定または補集合の型のために予約されているため、漸進的一貫性関係をA ~ Bではなくconsistent(A, B)として書くべきです。
Dynamic[T]の表示は診断識別子システムに従います。dynamic.*ファミリー外の診断は、動的起源がほとんどのユーザー向けメッセージにとって付随的であるため、ラップされた形式ではなく絞り込まれた静的ファセットTに小さなfrom untyped provenance注を付けてレンダリングします。dynamic.*内の診断とrigor explainを通じて要求された説明は、完全なDynamic[T]形式を表示します。内部トレース・キャッシュキー・プラグインのScopeクエリは、表示選択に関係なく常にラップされた形式を保持するため、プラグインとリファクタにわたる推論チェーンは安定したままです。
3値の確信度は証明システムの近道ではない
Section titled “3値の確信度は証明システムの近道ではない”関係クエリは、不確実性が意味あるときyes・no・maybeを返すべきです。yesとnoは、現在のソース・受け付けられたシグネチャ・プラグインファクト・解析器の仮定の下で証明された結果のために予約されています。maybeは、解析器がどちらの側も証明できないことを意味します。
受け付けられたメソッドシグネチャは、依然として信頼できるメソッド境界契約を定義します。引数と呼ばれたメソッドの戻り値は、受け付けられたRBS・rbs-inline・Steep互換・生成された・RBS::Extended契約に従って解析されます。Rigorは、実装が別の場所にあるという理由だけで他のメソッドから受け取ったすべての値を不確実な値にしません。
maybeは関係がyesであるかのように絞り込むには十分ではなく、関係がnoであるかのように反対側エッジを示唆もしません。診断のために弱い関係的・メンバー存在・動的起源・プラグインprovenanceファクトとして保持されてかまいません。繰り返しのmaybe証拠は、より強い証明が提供されない限りmaybeのままです。
maybeの診断はポリシー駆動です。PHPStanのエラーレベルのように、Rigorは、それらを報告せずにmaybe依存の呼び出しを受け入れる寛容なレベルと、不確実なメソッド呼び出し・役割マッチ・分岐証明を報告するより厳格なレベルの余地を残すべきです。
maybeと不完全な推論は別個の概念であり、混同されてはいけません。
maybeは関係クエリの結果です。部分型関係・構造的互換性・メンバー存在のような問いに3つの値で答えます。推論が完全であっても適用されます。すなわち解析器は単に利用可能な証拠の下でどちらの側も証明できないだけです。- 不完全な推論は、予算カットオフ(再帰深度・コールグラフ幅・演算子の曖昧さなど)によって引き起こされる解析器の結果です。それは理由とプレースホルダー型(
Dynamic[top]または別の保守的な不完全推論マーカー)を伴うstatic.*診断を生成します。カットオフは特定の関係的問いから独立しています。 - 2つは合成します。プレースホルダー型に対する関係クエリは、欠けている精度が
yes/noの答えを妨げるためmaybeを返すことが許されますが、根本的な原因はカットオフであり、診断はそうとして識別します。実装者は、ユーザーからカットオフを隠す関係的maybeに「早期停止」を縮退してはいけません。
PHPStanとRBSの比較
Section titled “PHPStanとRBSの比較”references/phpstan/website/src/writing-php-code/のPHPStanドキュメンテーションは、動的言語のための成熟した解析器の機能サーフェスを記述するため有用です。PHPStanは互換性ターゲットではなく、PHPDoc構文はRigor構文になるべきではありませんが、その機能は精密な静的解析からユーザーが最終的に期待するものの強いチェックリストです。
| 領域 | PHPStan | RBSとRigorの含意 |
|---|---|---|
| 注釈境界 | PHPStanは関数・メソッド・プロパティ・クラス・ローカル変数・ベンダースタブファイル上のPHPDocタグとPHPネイティブ型ヒントを組み合わせます。PHPDocはPHP構文が弱すぎるときネイティブヒントを補完します。 | RigorはRubyのアプリケーションコードをRigor固有の注釈DSLから自由に保ちます。RBS・rbs-inline・Steep互換の注釈は受け入れられた型ソースであり、RBS::Extended注釈または外部シグネチャはRigor専用の追加ファクトの場所です。インラインRubyコメントは、Rigor固有のリファインメントの主要な訂正機構になるべきではありません。 |
| 信頼と真実のソース | PHPStanはインラインの@varアサーションを信頼し、より良いPHPDoc・スタブ・ジェネリクス・アサーション・拡張でソースで型を直すことを推奨します。 | RigorはローカルオーバーライドコメントよりRBSシグネチャ・生成されたファクト・チェックされたアサーションを好むべきです。将来のローカルオーバーライドは目に見えて安全でなく、診断なしに推論されたファクトを黙って置き換えるべきではありません。 |
| 動的型 | PHPStanのmixedはチェックされない操作を許します。それは欠けた型によって引き起こされる暗黙のmixedを明示的なmixedから区別し、より厳格なルールレベルでそれで何ができるかを制限します。 | RBSのuntypedは動的型です。Rigorは動的起源のprovenanceを保つべきで、これにより厳格モードは意図的なuntypedを欠落または推論不能の情報から区別できます。 |
| 基本スカラーとオブジェクト型 | PHPStanはPHP形のスカラー・オブジェクト・リソース・callable・iterable・クラス/インターフェイス名を持ち、加えてintやintegerのようなエイリアスを持ちます。 | RigorはRBSとRubyの名前を標準として使うべきです。PHPエイリアスは参考資料のみです。Rubyのコアクラス・シングルトンクラス型・インターフェイス・RBS組み込みがサーフェスを定義します。 |
voidとbottom | PHPStanは有用な戻り値がないことにvoidを使い、早期終了する呼び出しにneverのようなエイリアスを使います。@return neverはexitやリダイレクト後の未定義変数解析にも役立ちます。 | RBSはすでにvoidとbotを持っています。Rigorはvoidを未使用の戻り値マーカーとして保ち、戻らない制御フロー・早期終了・不可能な分岐・網羅性にbotを使うべきです。 |
| Union・交差・括弧 | PHPStanはPHPDoc型でunion・交差・グループ化をサポートします。 | RBSはすでにunionと交差をサポートしています。Rigorは境界でRBS構文を保ち、精密な診断のために内部で正規化を使うべきです。 |
| リテラルとconstant型 | PHPStanはスカラーリテラルとクラスまたはグローバル定数を受け入れ、ワイルドカードのような定数列挙を含みます。 | RBSはリテラル型をサポートします。Rigorは内部でリテラルunionと選択された定数展開を使えますが、定数パターン列挙はPHPのクラス定数構文ではなくRubyの定数セマンティクスに従うべきです。 |
| 整数リファインメント | PHPStanはpositive・non-zero・境界付き整数区間のような名前付き整数リファインメントと範囲を持ちます。 | Rigorはpositive-intやnon-zero-intのような有用なリファインメントを保つべきですが、Integer[1..10]のようなRuby/RBS形の範囲表記がPHPStan構文より好ましいです。 |
| 文字列リファインメント | PHPStanはnon-empty-string・literal-string・numeric-string・caseリファインメント・decimal-int文字列・PHP真偽値指向の文字列型を持ちます。 | RigorはRubyに意味のあるリファインメントのみを取り入れるべきです。non-empty-string・literal-string・numeric-string・lowercase-string・uppercase-string・decimal-int-stringはもっともらしいです。Ruby文字列は常に真値であるため、truthy-stringのようなPHP真偽値の綴りは有用ではありません。 |
| 配列・リスト・iterable | PHPStanは同質配列・空でない配列・連続整数キーを持つリスト・キーと値の型を持つiterable・collection的なtraversableクラスを区別します。 | RBSは配列・タプル・レコード・enumerable的ライブラリシグネチャを持ちます。Rigorは有用なときに配列/リスト/iterator要素ファクトを推論すべきですが、RubyのArray[T]・タプル・レコード・ライブラリRBSがエクスポート形式のままであるべきです。 |
| 配列とオブジェクトのシェイプ | PHPStanの配列シェイプは必須キーとオプショナルキー・タプルのような数値キー・クラス定数キー・list{...}・空でないシェイプ形式をサポートします。オブジェクトシェイプは公開の読み取り専用プロパティを記述し、書き込み性を取り戻すために交差を使います。 | RBSのレコードとタプルはこの空間の一部をカバーします。Rigorはオプショナルキー・open/closed extra-keyポリシーを含むよりリッチなhash・キーワード・タプル・オブジェクトシェイプを内部で推論し、それからそれらをRBSレコード・タプル・Hash[K, V]・インターフェイス・名前的型・topに消去すべきです。 |
| キーと値の射影 | PHPStanはkey-of・value-of・配列とジェネリック属性マップに特に有用なT[K]のようなオフセットアクセスを持ちます。 | Rigorはレコード・タプル・hash・キーワード引数・オブジェクトシェイプのキー射影・値射影・インデックスアクセスのセマンティクスを、key_of[T]・value_of[T]・T[K]のような標準形式を使ってサポートすべきです。 |
| 型エイリアス | PHPStanはグローバルに設定されたエイリアスに加えてローカルな@phpstan-typeエイリアスと@phpstan-import-typeをサポートします。 | RBSはすでに型エイリアスを持っています。Rigorは共有名にRBSエイリアスを使い、第2のエイリアスシステムを追加するのではなく、通常のRBSが表現できないファクトのためにRBS::Extendedメタデータを予約すべきです。 |
| ジェネリクスと分散 | PHPStanは@template・境界・デフォルト・宣言位置の分散・呼び出し位置の分散・スター射影でジェネリッククラス・インターフェイス・トレイト・関数・メソッドを定義します。 | RBSはすでに宣言のためのジェネリクスと分散を持っています。RigorはRBSのジェネリック境界を保ち、呼び出し位置の分散と未知引数射影を将来の内部チェックツールとして検討し、PHPDocのテンプレート構文の取り入れを避けるべきです。 |
| 条件付き・依存戻り値 | PHPStanの条件付き戻り値型・template-type・newは引数型・ジェネリック引数・クラス名文字列に依存する戻り値型を表現します。 | Rigorは引数感受性・レシーバー感受性の戻り値ファクトを推論・オーバーロード選択・RBS::Extendedメタデータとしてモデル化すべきです。クラス名文字列射影はクラスオブジェクトとsingleton(C)より中心的ではありません。 |
| クラス名文字列 | PHPStanはclass-string<T>・interface-string・trait-string・enum-stringを持ち、class_existsのような呼び出しによって絞り込まれます。 | Rubyはクラスとモジュールのオブジェクトを直接渡せます。Rigorはsingleton(C)とオブジェクトレベルのファクトを好むべきです。文字列からクラスへの射影は委ねられ、Rubyの定数ルックアップとファクトリーAPIを中心に設計されるべきです。 |
| Callableの精度 | PHPStanのPHPDocはcallableシグネチャ・純粋callable・ジェネリッククロージャ・参照渡し引数・可変長引数・即時対遅延呼び出し・クロージャの$this再束縛を指定できます。 | RBSはすでにメソッド・proc・ブロック・オーバーロード・オプショナル・キーワード・rest・self関連形式を持っています。Rigorはフロー解析に影響するときRubyのブロック・proc・lambda・レシーバー束縛・純粋性・呼び出しタイミングを別個のファクトとしてモデル化すべきです。 |
| マジックメンバーとmixin | PHPStanは@property・@method・@mixinを使って__get・__set・__call・委譲・フレームワーク的動的APIを記述します。 | Rubyはmethod_missing・respond_to_missing?・委譲・include・extend・メタプログラミングを持ちます。Rigorは可能な限りこれらをコア構文の外に保ち、RBSメンバー・インターフェイス・生成されたシグネチャ・将来のプラグインファクトを通じて表現すべきです。 |
| フローナローイング | PHPStanは厳密な比較・型チェック関数・instanceof・assert・アサーションライブラリ・カスタム型指定拡張を通じて絞り込みます。 | Rigorは等価性・nil?・is_a?・kind_of?・instance_of?・respond_to?・パターンマッチング・return・raise・アサーション・プラグインファクトを使ってRuby固有のCFAを実装すべきです。絞り込まれたファクトは、シグネチャメタデータが動機であった場合でも内部的です。 |
| 述語とアサーションメタデータ | PHPStanの@phpstan-assert・@phpstan-assert-if-true・@phpstan-assert-if-falseは、否定アサーションとtrueのみの等価アサーションを含めて、引数・プロパティ・メソッド戻り値を絞り込めます。 | RBSは述語戻り値型を持ちません。Rigorはアサーション挙動にRBS::Extendedフロー効果を使い、肯定的・否定的分岐ファクトをサポートし、false分岐が補集合を含意しないときtrueのみの絞り込みのために明示的な形式を許すべきです。 |
| Outとself効果 | PHPStanは@param-outで参照渡し出力引数を、@phpstan-self-outまたは@phpstan-this-outでレシーバー型変更を記述できます。 | RubyはPHPスタイルの参照渡し引数を持ちませんが、メソッドはレシーバーと引数を変更できます。Rigorはレシーバー・引数・インスタンス変数・シェイプ変異を通常の戻り値型としてではなく効果としてモデル化すべきです。 |
| 例外・非推奨・内部API | PHPStanは@throws・@deprecated・@not-deprecated・@internalのようなタグを読み、よりリッチなポリシーのための拡張があります。 | これらは値型より、シンボルと制御フロー周りの解析機能です。Rigorは最終的にRBS宣言またはプロジェクト設定に同等のファクトを取り付けるべきで、コア値型言語を集中させ続けるべきです。 |
| 拡張と設定 | PHPStanはスタブファイル・動的戻り値型拡張・型指定拡張・パラメータout拡張・クロージャ拡張・早期終了呼び出し設定・拡張パッケージを公開します。 | これは強くRigorのプラグイン方向をサポートします。フレームワーク・ライブラリ固有のファクトは、フレームワークの挙動をコア解析器にハードコードするのではなく、シグネチャ・設定・生成されたRBS・プラグインによって貢献されるべきです。 |
Rigorに対する主なPHPStanの教訓は、有用な静的解析が名前的シグネチャ以上のものを必要とすることです。ユーザーは精密なコレクションメンバー・シェイプ・callable挙動・フロー述語・マジックメンバー記述・ライブラリ固有のファクトを必要とします。Rigorは、RBSを安定した交換フォーマットとして保ち、Rubyソースコードを解析器固有のPHPDoc的コメントから自由に保ちつつ、それらの能力を提供すべきです。
TypeScriptとRBSの比較
Section titled “TypeScriptとRBSの比較”references/TypeScript-Website/packages/documentation/copy/en/handbook-v2/とreferences/TypeScript-Website/packages/documentation/copy/en/reference/のTypeScriptハンドブックと参考資料は有用な設計入力ですが、TypeScriptは互換性ターゲットではありません。RigorはTypeScriptの構文ではなく、RubyとRBSに合うセマンティックなアイデアを借用すべきです。
| 領域 | TypeScript | RBSとRigorの含意 |
|---|---|---|
| シグネチャ境界 | TypeScriptは通常.tsファイルで実装コードと型注釈を混ぜ、JavaScriptライブラリのために宣言のみの.d.tsファイルもサポートします。型注釈は出力されたJavaScriptから消去されます。 | RigorはRubyにTypeScriptのようなインライン構文を導入しません。RBS・rbs-inline・Steep互換の注釈はRubyエコシステム入力として受け入れられ、Rigor専用の内部精度は通常のRBSに保守的に消去しなければなりません。 |
| 外部型エコシステム | TypeScriptは組み込みのlib.*.d.ts・バンドルされたパッケージ宣言・DefinitelyTypedの@typesパッケージを使います。 | RigorはRubyコア・stdlib・gem・依存関係シグネチャをRBSに依存すべきです。TypeScript宣言は参考資料のみです。 |
| 互換性モデル | TypeScriptの互換性は主に構造的です。オブジェクト・インターフェイス・クラスインスタンス・ジェネリックの互換性は利用可能なメンバーに基づいており、privateとprotectedのクラスメンバーが名前的のような制約を追加します。 | RBSのクラスとモジュールは名前的のままです。RBSインターフェイスとRigorのオブジェクトシェイプが構造的橋渡しを提供します。Rigorはデフォルトですべてのクラスの代入可能性をTypeScriptスタイルの構造的にすべきではありません。 |
| 健全性(soundness)モデル | TypeScriptはJavaScriptのエルゴノミクスのために意図的にいくつかの不健全な挙動を受け入れます。これにはany・代入互換性・一部モードの関数引数の双変性・optional/rest引数規則・ローカルの過剰プロパティヒューリスティクスが含まれます。 | Rigorはuntyped・漸進的一貫性・プラグインファクト・診断を通じて不健全性を見えるようにすべきです。TypeScriptの代入互換性を全面的にコピーすべきではありません。 |
| 動的・top・unknown値 | anyはチェックを無効化し動的に伝播します。unknownは任意の値を保持できますが、使用前に絞り込みが必要です。objectはプリミティブを除外します。neverはbottomです。 | RBSはすでにuntyped・top・botを持っています。Rigorはanyのアイデアをuntypedに、unknownの安全top役割を主にtopプラスチェックされた操作に、neverをbotにマップします。TypeScriptの綴りは標準のRigorの綴りになるべきではありません。 |
void | TypeScriptのvoidは主に関数戻り値型です。値を返す関数はvoidcallback型に代入可能になり得ますが、直接のfunction f(): voidの本体は値を返せません。 | RBSのvoidは未使用の戻り値マーカーです。Rigorはvoid結果に代入したりメッセージを送ったりすることが診断であるよう区別を保つべきで、Rubyのブロックセマンティクスの理由なしにTypeScriptのcallback固有のvoid代入可能性を取り入れるべきではありません。 |
| 不在性とnil可能性 | TypeScriptはnullとundefinedの両方を持ちます。オプショナルプロパティはstrictNullChecks下でundefinedの可能性として読まれます。非null assertion !は実行時チェックなしに`null | undefined`を除去します。 |
| 真偽値性 | JavaScriptの偽値値は0・NaN・""・0n・null・undefinedを含みます。TypeScriptはそのモデルの周りで絞り込みます。 | Rubyの偽値値はfalseとnilのみです。Rigorは制御フローナローイングのアイデアを借用すべきですが、Rubyの真偽値性を使わなければなりません。truthy-stringやnon-falsy-stringのような型はRubyの精度を加えません。 |
| オブジェクトとhashシェイプ | TypeScriptのオブジェクト型は、必須・オプショナル・readonlyプロパティ・インデックスシグネチャ・オブジェクトリテラルの過剰プロパティチェック・キー上のmapped変換を持つプロパティバッグを記述します。 | RBSはレコード・タプル・インターフェイス・名前的クラスを持ちますが、完全なTypeScriptのオブジェクト型計算ではありません。Rigorはよりリッチなhash・キーワード・オブジェクトシェイプを内部で推論し、それからそれらをRBSレコード・Hash[K, V]・インターフェイス・名前的基底・topに消去できます。 |
| 可変性修飾子 | TypeScriptはreadonlyプロパティ・ReadonlyArray・readonlyタプル・readonlyとオプショナル性を追加または除去するmapped修飾子を持ちます。これらはコンパイル時の使用制限であり、深い実行時不変性を含意しません。 | Rigorは読み取り専用ビュー・フローズン値・シェイプエントリー可変性・writer利用可能性を別個のファクトとしてモデル化すべきです。それらはRBSが後で標準化しない限り、通常の名前的値型になるべきではありません。 |
| Union・交差・リテラル・タプル型 | TypeScriptはunion・交差・string/number/booleanリテラル・判別union・配列・タプルをサポートします。リテラル推論はlet・const・オブジェクト可変性・as constに敏感です。 | RBSはすでにunion・交差・リテラル・配列・タプルをサポートします。Rigorはリテラル精度を内部で保ち、変異・エイリアス・パフォーマンス・RBS消去が要求するときに広げるべきです。 |
| フローナローイング | TypeScriptはtypeof・真偽値性・等価性・in・instanceof・代入・到達可能性・ユーザー定義型述語・assertion関数・判別union・never網羅性チェックで絞り込みます。 | Rigorはnil?・is_a?・kind_of?・instance_of?・respond_to?・等価性・パターンマッチング・return・raise・プラグインファクトのようなガードを使ってRuby固有のCFAを実装すべきです。述語とassertion挙動は通常の戻り値型ではなくRBS::Extendedフロー効果に属します。 |
| 型述語 | TypeScriptはparameter is Typeのような戻り値型として述語を書き、クラスはthis is Typeを使えます。 | RBSは同等の戻り値型形式を持ちません。Rigorは通常のRBSシグネチャ上のrigor:v1:predicate-if-true value is Tのような注釈としてこれらを表現すべきです。 |
| 網羅性 | TypeScriptはすべてのunion代替が除去された後、しばしば網羅的なswitchチェックのためにneverを使います。 | Rigorは不可能な分岐と、有限リテラルunion・sealed的プラグインファクト・パターンマッチ上の網羅性にbotを使うべきです。標準の綴りはbotのままです。 |
| 型レベル演算子 | TypeScriptはkeyof・型コンテキストtypeof・インデックスアクセス型・inferを伴う条件型・分配条件型・mapped型・テンプレートリテラル型、およびPartial・Pick・Omit・Exclude・Extract・NonNullableのようなユーティリティ型を持ちます。 | RBSは比較可能な一般的な型レベル計算を持ちません。Rigorはkey_of[T]・value_of[T]・T[K]・T - U・T & UのようなRigorネイティブ形式と将来の条件型構文を通じて選択されたセマンティクスをサポートできます。具体的な移行利益が現れない限り、TypeScriptの演算子とユーティリティ名の取り入れを避けるべきです。 |
| ジェネリクスと分散 | TypeScriptのジェネリック型パラメータは、それらがメンバーで使われる場所でのみ構造的互換性に影響します。分散は構造的使用から推論され、明示的な分散注釈はインスタンス化ベースの比較に限定されます。 | RBSのジェネリクスは、RBS独自の分散規則で名前的とインターフェイス定義・エイリアス・メソッド・procに宣言されます。RigorはRBSのジェネリック境界を保ち、シェイプまたはインターフェイスを比較しているところでのみ構造的分散推論を使うべきです。 |
| 関数とオーバーロード | TypeScriptは関数型式・呼び出しシグネチャ・constructシグネチャ・オーバーロードシグネチャ・実装シグネチャ・消去されたthis引数・callbackの文脈型付けを持ちます。 | RBSはメソッド型・proc型・ブロック・オーバーロード・self・instance・class・singleton(C)を持ちます。RigorはTypeScriptのcall/construct構文を取り入れるのではなく、Rubyのメソッド・ブロック・proc・singletonメソッド・クラスオブジェクトを直接モデル化すべきです。 |
| クラスとオブジェクト構築 | TypeScriptクラスはインスタンス側とstatic側の両方の型を作ります。constructシグネチャとInstanceTypeはコンストラクタ関数をインスタンスに関連付けます。implementsは適合をチェックしますが、クラス本体の推論された型を変えません。 | Rubyのクラスオブジェクトは通常のオブジェクトであり、RBSはsingleton(C)でクラスオブジェクト型を綴ります。将来のインスタンス射影は、JavaScriptのコンストラクタ関数型ではなくRubyのクラスオブジェクトとファクトリーメソッドを中心に設計されるべきです。 |
| 宣言マージと名前空間 | TypeScriptは宣言にわたってインターフェイス・名前空間・クラス・関数・enumをマージでき、宣言は異なる名前空間・型・値エンティティを作成できます。 | Rubyはすでに再開可能なクラスとモジュールを持ち、RBSは独自の宣言モデルを持っています。RigorはTypeScriptの宣言マージを型機能として取り入れるべきではありません。RBSとRubyの定数セマンティクスに従うべきです。 |
| Enum・JSX・decorator・symbol | TypeScriptはenum・JSX・decorator・unique symbol・well-known symbolのJavaScript向け機能とドキュメンテーションを含みます。 | これらはRBS型システムのターゲットではありません。RigorはTypeScript固有の実行時またはプラットフォーム構成ではなく、Rubyのリテラル・定数・シンボル・モジュール・クラス・プラグインファクトを使うべきです。 |
Rigorにとって特に重要なTypeScriptの教訓は2つあります。
第1に、フロー感受性解析はオプショナルではありません。TypeScriptの有用な診断は、宣言された型とプログラム位置で観測される型の差を保つことに依存します。Rigorはローカル・インスタンス変数・メソッドレシーバー・ブロック引数・シェイプメンバーについて同じ区別を必要とします。
第2に、TypeScriptの型レベル計算は強力ですが、JavaScriptのオブジェクトキーとプロパティアクセスに密に結合されています。Rigorは、RBS境界を小さくRuby形に保ちながら、レコード・タプル・hash・キーワード引数・オブジェクトシェイプ・プラグイン提供のファクトのための設計インスピレーションとしてそれらの演算子を使うべきです。
PythonのtypingとRBSの比較
Section titled “PythonのtypingとRBSの比較”references/python-typingツリーは有用な参考資料ですが、Pythonのtypingは互換性ターゲットではありません。Rigorは概念がRubyのセマンティクスを保ちRBSに消去できるときにのみ借用すべきです。
| 領域 | Pythonのtyping | RBSとRigorの含意 |
|---|---|---|
| シグネチャ境界 | Pythonはインライン注釈と別個のスタブを許します。 | RigorはPythonのようなRigor固有のインライン注釈システムを避け、RBS・rbs-inline・Steep互換の注釈をRubyエコシステムのシグネチャ入力として使います。 |
| 動的型とtop型 | Anyは未知の漸進的型ですが、objectは最大の完全静的オブジェクト型です。 | RBSはすでにRigorにuntypedとtopを与えています。Pythonの実体化と代入可能性モデルはそれらを別個に保つことを補強します。 |
| 構造的型 | ProtocolとTypedDictは明示的な型付け規則を持つ構造的型形式です。 | RBSのインターフェイスとレコードはこの空間の一部をカバーします。Rigorはよりリッチなオブジェクトとhashシェイプを内部で推論し、それからそれらをRBSのインターフェイス・レコード・Hash[K, V]・topに消去できます。 |
| Hashシェイプの詳細 | TypedDictは必須・非必須・読み取り専用・open・closed・extra項目を区別します。 | RigorはRubyのhash・options-hash・キーワード引数のシェイプにこの語彙を再利用すべきですが、別のファクトが証明しない限り通常のRubyのhashが可変であることを覚えておきます。 |
| クラスオブジェクトとself型 | Pythonはtype[C]・Self・コンストラクタ固有の規則を使います。 | RBSはすでにsingleton(C)・self・instance・classを持っています。Rigorはそれらの形式を好み、将来のインスタンス射影をRubyのクラスオブジェクトを中心に設計すべきです。 |
| Nil的型とbottom型 | Noneは通常の値型です。NeverとNoReturnはbottomエイリアスです。 | RBSはnil・NilClass・bot・voidを区別します。RigorはPythonのエイリアスを取り入れるべきではなく、voidはRBS固有の未使用戻り値マーカーのままです。 |
| 型述語 | Pythonは肯定のみのナローイングのためにTypeGuardを、肯定と否定のナローイングのためにTypeIsを持ちます。 | RBSは述語戻り値型形式を持ちません。RigorはRBS::Extended注釈内のフロー効果としてこれらをモデル化すべきです。 |
| メタデータ | Annotated[T, ...]はメタデータを理解しないツールによってTとして扱われます。 | RBSの%a{...}注釈はRigorにRBS::Extendedメタデータのために同じ互換性パターンを与えます。 |
| Callable精度 | Pythonはオーバーロードマッチング・位置とキーワード引数の種類・ParamSpec・TypeVarTuple・Unpack[TypedDict]を指定します。 | RBSはすでにメソッドとprocシグネチャ・オーバーロード・オプショナルとキーワード引数・ブロック型を持ちます。Rigorはチェック原則とキーワードシェイプのアイデアを借用すべきで、Python構文は借用すべきではありません。 |
| 可変性とfinality | PythonはFinal・ClassVar・ReadOnly・@final修飾子を持ちます。 | Rigorは、必要に応じて、これらを通常の値型ではなくシンボル・メンバー・シェイプ書き込みファクトとしてモデル化すべきです。 |
構造的インターフェイスがプロトコル橋渡しである
Section titled “構造的インターフェイスがプロトコル橋渡しである”PythonのProtocolは、静的ダックタイピング(duck typing)に名前付きの契約を与えるため価値があります。すなわちプロトコルから継承していなくても、必要なメンバーを互換性のある型で持つときオブジェクトは受け入れ可能です。
RigorはPython構文を取り入れるのではなく、RBSインターフェイスと推論されたオブジェクトシェイプを通じて同じ恩恵を得るべきです。_ClosableのようなRBSインターフェイスは、名前付きの構造的契約として扱えます。名前的クラス・シングルトンオブジェクト・モジュールオブジェクト・無名オブジェクトシェイプは、Rigorがすべての必要なメンバーを互換性のあるメソッドまたは属性挙動で持つことを証明できるとき、そのインターフェイスを満たせます。
これは擬似プロトコルモデルです。
- RBSインターフェイス宣言は構造的契約に安定した名前を提供します。
- Rigorのオブジェクトシェイプは無名で推論生成された構造的型を提供します。
- インターフェイスへの代入可能性は構造的に証明されてかまいません。Rubyの継承・
include・実行時マーカーは要求されません。 - 明示的なRBS宣言または将来の
RBS::Extendedメタデータは、Rigorに早期適合性検証を要求してかまいませんが、構造的代入可能性は明示的なオプトインを要求すべきではありません。 respond_to?のような実行時チェックはメンバー存在ファクトを提供できますが、それ単独で完全なシグネチャ互換性を証明すべきではありません。
可変なプロトコル属性が不変であるというPythonの規則は、Rubyのメソッドケイパビリティ(capability)にきれいにマップします。読み取り専用属性はリーダーメソッドであり、その結果で共変(covariant)であり得ます。書き込み専用属性はライターメソッドであり、受け入れる値で反変(contravariant)にチェックされます。読み書きアクセサは両方の制約を組み合わせ、実質的に不変です。
メソッドシェイプと可視性
Section titled “メソッドシェイプと可視性”リーダーとライターケイパビリティはフィールド宣言ではなくメソッドケイパビリティです。attr_reader・attr_writer・attr_accessorはメソッドファクトのソースです。Rigorは結果のxとx=メソッドをシェイプ上の別個のエントリーとしてモデル化します。
attr_reader :xは、周囲のRubyの可視性状態が変えない限り、公開リーダーメソッドxを貢献します。attr_writer :xはライターメソッドx=を貢献し、リーダーを含意しません。attr_accessor :xは両方のメソッドを貢献しますが、Rigorは依然としてそれらを2つのメソッドエントリーとして保ちます。- 手動で定義またはオーバーライドされた
xまたはx=メソッドは、通常のRubyのメソッドルックアップとソース順に従ってメソッドファクトを置き換えまたは精緻化します。リーダーとライターケイパビリティは純粋性を含意しません。
可視性はすべてのメソッドシェイプエントリー上の第一級ファセットです。Rigorは少なくともpublic・protected・private、加えてメンバーが使われる呼び出しコンテキストを追跡します。
- 外部の明示的レシーバー送信は公開メソッドを要求します。
- privateメソッドはprivate呼び出しコンテキストでのみ呼ばれ、通常の明示的レシーバー送信としては呼ばれません。
- protectedメソッドはRubyのprotectedレシーバー制限に従い、デフォルトでは公開構造的インターフェイス要件を満たしません。
- 公開構造的インターフェイスは、内部チェックまたは将来のインターフェイス形式が明示的に別の可視性を要求しない限り、公開メンバーを要求します。
respond_to?チェックは、オブジェクトを完全なシグネチャ互換性ではなく存在のみのシェイプに精緻化します。オプショナルなinclude_private引数は可視性ファクトを変えます。
obj.respond_to?(:foo)とobj.respond_to?(:foo, false)はtrue分岐でfooの公開存在ファクトを生成します。obj.respond_to?(:foo, true)は、可視性がpublic・protected・privateであり得る存在ファクトを生成します。それ自体ではobj.fooが外部の明示的レシーバー呼び出しとして合法であることを証明しません。- 第2引数が静的に分かっていないとき、Rigorはより弱いmaybe-private可視性ファクトを記録します。
respond_to_missing?とmethod_missingファクトは動的provenanceと未知またはプラグイン提供のシグネチャを運びます。それらはガードされた動的呼び出しを正当化できますが、それ単独で完全なインターフェイス互換性を証明しません。
最小の最初の実装表現は、1つのメソッドシェイプエントリーを1つの解決されたRubyメソッド本体とペアにします。
MethodEntryは(クラスまたはモジュール、メソッド名)ごとに1レコードです。Rubyは実行時にシグネチャごとのオーバーロードを持たないため、エントリーはそのクラスまたはモジュール上のその名前に対する実行時解決のメソッド本体に対応します。- 単一のソースレベル
def fooはMethodEntryを生成する最も単純な入力です。同じクラスまたはモジュール上の複数のソースレベルdef foo定義 — 単一ファイルから・ファイルにまたがって分割された部分クラスから・モンキーパッチから・prependとincludeチェーンから — は、別個のエントリーではなくマージ候補として同じエントリーに供給されます。 - 可視性は
MethodEntryレベルに保存されます。Rubyのprivate :fooは特定のシグネチャバリアントではなくメソッド全体を切り替えるため、オーバーロードごとの可視性は最初のバージョンでは表現されません。 - RBSオーバーロード・
RBS::Extendedペイロード・プラグイン貢献からのシグネチャバリアントは、エントリー内のブランチのリストとして保存されます。ブランチはエントリーの可視性を共有しますが、異なる引数シェイプ・戻り値型・述語効果・変異効果を運んでかまいません。 - 条件付き
def・条件付きprivate・他の動的に構築されたメソッド定義は最初の実装のスコープ外です。それらは通常の診断または動的起源ファクトとして表面化し、後で再検討されてかまいません。
オープンクラス・モンキーパッチ・祖先チェーン挿入はすべて同じMethodEntryに供給します。
- 各候補
def foo(元のクラスから・再開されたクラスから・このレシーバーで解決されるprependまたはincludeされたモジュールから・Rigorに可視性のあるリファインメントスコープから)は1つの入力を貢献します。 - デフォルトのマージポリシーはRubyの実行時解決に従います。すなわちRubyが実際にディスパッチする候補が勝ちます。通常の同一クラス再定義の中ではこれは最後の定義が勝つ解決を伴うソース順です。祖先チェーンの中ではこれはRubyが
prependをクラスより優先し、includeされたモジュールをsuperclassチェーンより優先して使うルックアップ順です。 - 厳格モードは、明示的なoverrideマーカーなしに再定義がRBSに見えるシグネチャまたは可視性を変えるとき診断を発生させます。意図されるoverrideマーカーは将来の
RBS::Extendedディレクティブ(作業名rigor:v1:override=replace)です。それが存在するまで、厳格モードは疑わしい黙ったモンキーパッチを報告します。 - モジュールincludeとリファインメントはホストクラスの
MethodEntryにフラット化されません。それらは所有モジュール上に残り、祖先チェーンを通じてルックアップに参加します。
ケイパビリティロールはアドホックなモックunionを上回る
Section titled “ケイパビリティロールはアドホックなモックunionを上回る”Rubyライブラリはしばしば、継承で関連していないが、メソッド本体が要求するケイパビリティを共有するオブジェクトを受け入れます。IOとStringIOが中心的な例です。すなわちStringIOは多くのストリーム消費者にとってインメモリのテストダブルとして有用ですが、IOのサブクラスではなく、完全なIOメソッドサーフェスを公開しません。
RigorはこれをStringIO <: IOを宣言することでモデル化すべきではありません。実装が単に小さなストリームケイパビリティを必要とするだけのとき、IO | StringIOのような繰り返しの宣言にユーザーを押しやることも避けるべきです。代わりに、Rigorは読み取り可能・書き込み可能・巻き戻し可能・閉じることが可能・シーク可能・ファイルディスクリプタバックのような構造的ケイパビリティロールを推論しサポートすべきです。
これは3つのファクトを別個に保ちます。
IOは実際のIOオブジェクトとファイルディスクリプタバック挙動を要求するAPIの名前的型です。StringIOはいくつかのストリーム役割を満たせる別個の名前的型です。- メソッドの推論された引数要件は、読み取り可能で巻き戻し可能なストリーム挙動のような、より小さなオブジェクトシェイプまたは名前付きインターフェイスかもしれません。
明示的なRBS宣言は依然として公開契約を定義します。シグネチャがIOと言うとき、StringIOを渡すことは部分型として黙って受け入れられるべきではありません。Rigorは代わりに、実装がより小さなケイパビリティロールのみを要求するように見えると報告し、シグネチャをインターフェイスに一般化することを示唆してかまいません。実装がIOとStringIOの両方からクラス固有の挙動で実際に分岐したり使用したりするとき、Unionは依然として適切です。
Rigorは、最初のプラグインマイルストーンに独自の名前を発明させるのではなく、共通の標準ライブラリケイパビリティロールの独自のコアカタログを出荷すべきです。カタログは、Rubyと標準ライブラリがすでに提供する場所では既存のRBS定義インターフェイスを再利用し、既存のインターフェイスが欠けているか、別個のケイパビリティを混同してしまう場所でのみ、Rigor固有の役割の小さなセットを追加します。
初期に再利用されるRBSインターフェイス:
- 確立されたstdlibインターフェイスのための
_Each[T]・_Reader・_Writer・_ToS・_ToStr・_ToInt・_ToProc・_ToHash[K, V]・_ToA[T]・_ToAry[T]。Rigorはこれらを既存のRBSシェイプでマッチし、それらを再定義しません。 - 広いコレクションと順序付けプロトコルのための
Enumerable[T]とComparable。それらは新鮮な構造的役割ではなく名前的インターフェイスとして役割マッチングに参加します。
最初のマイルストーンのために導入されるRigor固有の役割(それぞれRigorのバンドルされたシグネチャに明示的なRBSインターフェイスとともに出荷されます):
| 役割 | 目的 | 必要なメンバー |
|---|---|---|
_RewindableStream | 最初から再生できるストリーム的オブジェクト | read・rewind |
_ClosableStream | ライフタイムを閉じることができるストリーム的オブジェクト | close・closed? |
_FileDescriptorBacked | 実際のIOを要求する診断を正当化する実OSバックのストリーム | fileno |
_Callable[**A, R] | callに応答する任意のもの。_ToProcとは異なる | call(*A) -> R |
プラグインは役割・追加の適合ファクト・役割固有の除外・不確実な適合性を追加してかまいませんが、このカタログ内の再利用されたRBSインターフェイスまたはRigor固有の役割を黙って置き換えることはできません。
ケイパビリティロール推論の規律
Section titled “ケイパビリティロール推論の規律”ケイパビリティロールはサマリーであり、検索ではありません。推論は有界で予測可能なまま留まらなければなりません。
- 各メソッドはキャッシュされた要件サマリーを持ち、これは必要なメンバー名・可視性・アリティ(arity)・キーワードとブロック要件・戻り値使用制約・変異要件・各引数とレシーバーのprovenanceを記録します。
- サマリーはデフォルトで無名のオブジェクトシェイプ要件です。それを既知のインターフェイスとして名付けることは、コア推論結果ではなくエクスポートと診断の便宜です。
- 要件推論はローカルかつ単調です。直接呼び出しは既存のシグネチャまたはキャッシュされたサマリーを再利用します。再帰的または相互再帰的なサマリーは広げるプレースホルダーを使い、小さな不動点予算まで反復します。
send・public_send・制約のないmethod_missing・型なし委譲を通じた動的ディスパッチは、すべての可能なターゲットを列挙しようとするのではなく、動的要件を記録します。- 名前付きインターフェイスマッチングはメンバー名と可視性でキー付けされたインデックスを使います。Rigorは安価にフィルタされた候補のみを比較します。候補セットが大きすぎる場合、無名シェイプが保たれ、一般化ヒントは抑制されます。
- 候補選択は決定的です。すなわち厳密なメンバーシグネチャマッチが最初、設定された標準ライブラリ役割が偶然のユーザーインターフェイスより前、より少ない追加の必要メンバーが次、それから安定したレキシカルな名前順です。その順序の最上位での曖昧さは、名前付きの提案が報告されないことを意味します。
- 役割の交差は許されますが有界です。最初の実装は、厳密な単一インターフェイスマッチ・明示的な標準役割バンドル・厳格な候補制限の下での小さな貪欲な交差を使ってかまいません。無界のセットカバー問題を解決しません。
- ジェネリック保存は役割マッチャーではなく同一性追跡によって処理されます。メソッドが受け取った引数オブジェクトと同じものを返すなら、Rigorは
[S < _Role] (S value) -> Sを推論してかまいません。本体が値を置き換えるか、委譲されたオブジェクトを返すかもしれないなら、Rigorは通常の推論された戻り値型を使います。
本体の推論されたケイパビリティ要件と宣言された名前的型が不一致のとき、Rigorは3つの明示的なレベルに沿ってエスカレートします。
- Diagnosticは呼び出し位置での真の型不一致のために予約されます。宣言された引数型を満たさない呼び出しは、本体がどう実装されているかに関係なく診断です。このレベルは無条件で、設定に依存しません。
- Hintは、本体の推論された役割が宣言された名前的型より厳密に小さく、構造的インターフェイスへの一般化が依然として型検査されるときに適用されます。ヒントは
hint.role-generalization.*カテゴリーの下で発出され、style.suggest_role_generalization設定スイッチによってゲートされます。デフォルトはオフで、これにより名前的契約を選んだライブラリ作者がそれから押し出されません。構造的機会を発見したいアプリケーションコードにオプトインするのが適切です。 - Silentは残りのケースのデフォルトです。すなわち推論された役割と宣言された型が互換性があり、ユーザーには一般化が提供されず、推論結果は呼び出し元とリファクタツールのために内部的に保持されます。プラグインとリフレクティブクエリは依然として
ScopeAPIを通じて推論された役割を観測できます。
3つのレベルは任意の与えられた位置で相互排他的です。Rigorは、同じ引数について呼び出しを拒否しかつ一般化ヒントを提供することは決してなく、公開された名前的契約を黙って構造的なものに書き換えることも決してありません。
制御フローナローイングは中心的である
Section titled “制御フローナローイングは中心的である”RigorはPHPStan・TypeScript・Pythonの型チェッカーと精神的に類似した適切なCFAとデータフロー解析を実行すべきです。
例えば、value == "foo"の後、true分岐はvalueを"foo"に絞り込めて、false分岐はvalueがすでに互換性のある信頼できるドメインを持つとき~"foo"として表示される否定的ファクトを運べます。比較はそれ自体で肯定的ドメインを作成しません。正確な演算子構文は暫定的ですが、セマンティックケイパビリティは必要です。
PythonのTypeGuardとTypeIsの区別は同じ設計方向をサポートします。すなわち述語挙動はフロー効果です。trueのみの述語はTypeGuard的挙動には十分です。trueとfalseのファクトのペア、またはT & ~Uとして表現されたfalseファクトは、TypeIs的挙動を提供します。
CFAは、条件式全体が評価された後だけでなく、条件式内でスコープを更新するのに十分にきめ細かくなければなりません。if foo == "foo" && foo == "bar"について、&&の右側は左側のtrueエッジによって生成されたスコープで解析されます。現在のドメインが"foo" & "bar"を不可能にするなら、true分岐全体がbotになります。同じ原則が||・!・unless・elsif・case・パターンマッチングに適用されます。
Rubyの等価性はメソッドディスパッチであるため、等価ナローイングは純粋に構文的な規則ではあり得ません。equal?・nil?・ブール値チェック・信頼できる組み込みリテラルドメイン・RBSまたはプラグインによって宣言された述語効果は型ファクトを生成できます。未知の==実装は、Rigorが値型リファインメントを正当化するメソッド情報を持たない限り、より弱い関係的ファクトを生成すべきです。
素のuntypedの等価性は動的起源の関係的情報のままです。v: untypedに続くv == "foo"は、独立したガードまたは信頼できる等価効果がそのナローイングを証明しない限り、Dynamic["foo"]にはなりません。
初期の信頼できる等価サーフェスは意図的に狭いです。
equal?は観測された参照に束縛された同一性ファクトを生成します。ファクトは再代入・エイリアスエスケープ変異・未知呼び出し・プラグイン宣言の効果によって無効化されます。- 組み込みリテラルドメインの等価性は、
String・Symbol・Integer・boolean・nilの有限リテラルセットに対してのみ、レシーバーディスパッチターゲットが分かっておりレシーバードメインがすでに互換性があるときにのみ信頼されます。 Floatリテラルナローイングはデフォルトで拒否されます。NaN・符号付きゼロ・無限大・数値強制が浮動小数リテラル上の網羅性を安全でなくします。関係的ファクトは依然として診断のために保たれてかまいません。Range・Regexp・Module・Class・===ベースのcase挙動は、それ自体では一般的な値ナローイングファクトを生成しません。それらは値ドメインを精緻化する前に特定のナローイング規則またはRBS/プラグイン効果を要求します。- ユーザー定義の
==・eql?・===は、明示的なRBSメタデータ・RBS::Extendedフロー効果・プラグイン宣言のtrueエッジとfalseエッジファクトに加えて任意の必要な安定性または純粋性仮定を通じてのみ、関係的ファクトから値ファクトに昇格されます。
ファクトの安定性と変異効果
Section titled “ファクトの安定性と変異効果”フローファクトはすべて同じ種類ではなく、異なる速度で無効化されます。
- ローカル束縛ファクトは、このスコープで名前がどの値を参照するかについてです。
- 捕捉ローカルファクトは、外側または内側のクロージャによって書き込まれる可能性のあるローカルについてです。
- オブジェクト内容ファクトはhashエントリー・インスタンス変数・オブジェクトシェイプメンバー・他のヒープ状態をカバーします。
- グローバルストレージファクトは定数・グローバル・クラス変数・類似の共有状態をカバーします。
- 動的起源ファクトはナローイング後でも
Dynamic[T]マーカーを保持します。 - 関係的ファクトは、まだ値型に還元されていない比較を記録します。
無効化はスコープ全体ではなく標的化されています。
- 未知のメソッド呼び出しは、エスケープした可能性のあるターゲット(オブジェクトシェイプ・hashエントリー・インスタンス変数・定数・グローバル・クラス変数)のヒープファクトを無効化してかまいません。それらはスコープ内のすべてのローカル束縛ファクトを無効化しません。
- ローカル束縛ファクトは、そのローカルへの代入まで通常のメソッド呼び出しを生き延びます。呼び出しは
xが参照するオブジェクトを変異してかまいませんが、xがそれを書き込むクロージャによって捕捉されない限り、xを再束縛できません。 - クロージャ書き込みは明示的な効果です。ブロック・proc・lambdaが外側のローカルを書き込むとき、Rigorは捕捉ローカル書き込みを記録します。即時の既知呼び出しはその書き込みを呼び出しエッジで適用します。エスケープまたは遅延クロージャは、エスケープ点後に書き込み可能な捕捉ローカルファクトを不安定にします。
- 高階呼び出しは「yieldはすべてを無効化する」という包括的な規則ではなく呼び出しタイミング効果を必要とします。初期カテゴリーは、ブロック呼び出しなし・既知カウントの即時非エスケープ呼び出し・未知カウントの即時非エスケープ呼び出し・遅延またはエスケープブロックストレージ・未知のブロック挙動です。
tap・then・yield_self・each_with_objectのようなコアメソッドは、最終的にブロックタイミング・戻り挙動・レシーバーまたは引数の変異についてのサマリーを持つべきです。それらのサマリーが存在するまで、Rigorはそのような呼び出しによって触れられたオブジェクト内容ファクトを弱めてかまいませんが、関連のないローカル束縛ファクトは保つべきです。
安定したファクトに対する最初の証明義務は具体的です。
- エスケープするクロージャによって書き込み可能でない再代入されないローカル。
- 不変なシングルトンまたは即時値。
- 関連する操作についてフローズンと証明された値。
- エスケープしていない新鮮な割り当て。
- 読み取り専用・純粋・標的化された変異挙動を宣言するRBS・
RBS::Extended・プラグイン効果。
最小の最初の実装は、カテゴリーバケット化されたファクトストアを不変なエッジごとのScopeスナップショットとペアにします。
- 各
Scopeは制御フローエッジでキー付けされた不変なスナップショットです。ジョイン・ナローイング・無効化はin-place変異ではなく構造的共有を通じて新しいスナップショットを生成します。 - スナップショット内では、ファクトは上記のカテゴリーをミラーするバケットに分割されます。すなわちlocal-binding・captured-local・object-content・global-storage・dynamic-origin・relationalです。無効化規則は特定のバケットに作用するため、未知のメソッド呼び出しはlocal-bindingに触れずにobject-contentを掃きます。
- 複数のターゲットにまたがる関係的ファクトは独自のバケットに住み、参加するターゲットのバケットが変更を記録するとき無効化されます。
Scopeの公開サーフェスはバケットを直接公開しません。プラグイン・ナローイング規則・診断はターゲットについてのファクトをScopeに尋ねます。バケットレイアウトは進化してかまわない内部最適化です。
プラグイン前の純粋性ポリシーは、再呼び出しにわたってメソッド呼び出し結果がどう記憶または忘れられるか、PHPStanスタイルの記憶された値がどうRigorのカテゴリーバケットにマップするかを制御します。
- メソッドはデフォルトで非純粋として扱われます。レシーバー上の非純粋メソッドを呼び出すと、レシーバーのオブジェクト内容バケットが無効化され、同じレシーバーへの先行呼び出しの記憶された値ファクトが破棄されます。
- 純粋性は権威あるソースが宣言したときにのみ有効になります。Rigorとともに配布されるコアRubyとstdlibのRBS・受け付けられた通常のRBSファイル・
RBS::Extended上の明示的なrigor:v1:pure注釈が初期ソースです。生成されたシグネチャとプラグイン貢献はそのティア内で純粋性を精緻化してかまいません。 - 設定スイッチは、繰り返しの呼び出しにわたるより強いナローイングを望むプロジェクトのために、デフォルトをPHPStanの「値返却は非純粋と宣言されない限り純粋」ポリシーに似たものにすべきです。スイッチはデフォルトを反転させますが、明示的な
pureまたは変異宣言を決してオーバーライドしません。 pureが任意のレシーバー変異・引数変異・ファクト無効化効果と組み合わされることは、RBS::Extendedマージ規則ですでに指定されているとおり、契約衝突のままです。
最初のユーザー可視マイルストーン(v1)は、慎重に選ばれた小さなコアおよびstdlibクラスのセットに対する組み込みの変異・純粋性・呼び出しタイミングサマリーを出荷します。固定セットを選ぶことでv1のナローイングサーフェスを正直に保ちます。すなわちこのセット外のコードは、より多く知っているふりをせずに「デフォルト非純粋」にフォールバックし、より早く精度を必要とする作者はRBS::Extended注釈またはプラグインを供給できます。
v1のカバーされたセットは以下のとおりです。
| クラス | 理由 |
|---|---|
Array | Rubyコードで最もよく変更されるコンテナ。<<・push・pop・replace(変更)とmap・select・+(純粋)の明確な区別。 |
Hash | キー付きストレージに対するArrayと同じ役割。merge!対merge、[]=対dup-then-modifyパターンに必要。 |
String | 純粋呼び出しを非純粋と誤ると実際の偽陽性を生むin-place変異の頻繁なターゲット(<<・gsub!・replace)。 |
Set | in-placeセマンティクスが不変合成と異なる、広く使われる唯一の非コアコレクション。add・delete・merge対` |
IO | 呼び出しタイミング(each_line・read・write)が実際のファイルディスクリプタバックのコードのフロー安定性を直接駆動する効果重いクラス。 |
StringIO | テストとパイプラインでIOの代わりとして使われる。ケイパビリティロールが一貫して振る舞うよう同じ呼び出しタイミングモデルが必要。 |
File | IOの上にパス束縛の副作用を追加する。File.openブロックタイミングとFile.writeは十分に一般的で、それらが欠けるとv1が顕著に弱まる。 |
Tempfile | _ClosableStreamと_FileDescriptorBacked役割と自然にペアになるライフタイム効果(作成・unlink)を持つ。 |
Pathname | ファイルシステムに触れるコードの一般的な境界。ほとんど純粋な変換と効果のあるメソッド(mkdir・rmtree)の小さなセット。 |
Logger | 代表的な効果のみのAPI。純粋/非純粋の分離が副作用のみの呼び出し元の診断をリグレッションさせないことを検証するのに有用。 |
v1のサマリーは、各クラスについて以下をカバーします。
- メソッドごとのレシーバー変異状態・引数変異状態・ファクト無効化効果。
- メソッドごとのブロック呼び出しタイミング(ブロックなし・既知カウントの即時非エスケープ・未知カウントの即時非エスケープ・遅延またはエスケープ・未知)。
- 過剰な約束をせずに行えるところでのメソッドごとの純粋性宣言。
このセット外のクラスは、純粋または非純粋と黙って仮定されません。それらは、通常のRBS・RBS::Extended・プラグインファクトが別途述べるまでデフォルトの非純粋ポリシーに従います。
v1.1ロードマップは3つの軸に沿ってカバレッジを拡張し、より大きなサーフェスが着地する間にv1の挙動が安定したまま留まるようフィーチャーフラグの背後にあります。
- 追加のコアクラス(
Numericとその子孫・Symbol・Range・Regexp・Proc・Method・Time・Date・DateTime)。 - 追加の広く使われるstdlib(
Date・JSON・URI・OpenStruct・Forwardable・明示的な変異サマリーが必要なComparableを持つクラス)。 - 解析器側のモデリングが安定したら、選択されたメタプログラミング隣接のコアAPI(
Module・Class・BasicObject)。
組み込みの変異サマリーはクローズドリストではありません。新しいエントリーは、それらを呼び出さないコードの意味を変えない限り、任意のマイナーリリースで追加されてかまいません。発表されたロードマップは契約ではなく計画の助けです。
voidは戻り位置マーカーである
Section titled “voidは戻り位置マーカーである”RBSはvoidをtop的だがコンテキスト制限されたものとして扱います。Rigorはvoidを、戻り値が使われるべきでないことを言う結果マーカーとして内部でモデル化すべきです。
Rigorの値コンテキスト復旧はRBSの「top的」扱いと整合的です。すなわちvoidの結果が値位置に到達するとき、復旧された型はtopであり、より厳格またはより弱い代替ではありません。RBS規則の上に乗るRigorの貢献は、値がvoidからの復旧によって位置に到達したことを記録し、それを主要な診断として表面化することです。これにより解析器はなぜtopが現れたかを説明でき、ユーザーは一般的なtopと共に生きることを学ぶのではなく呼び出し位置を直せます。生成されたRBSはRBSが許す位置でのみvoidを綴り続けます。
これはvoidメソッド呼び出しの結果を代入するような診断を可能にします。文コンテキストではvoidは問題ありません。値コンテキストではRigorは診断を報告し、topで復旧します。
正規化された結果サマリーでは、void | botはvoidに縮退します。常にraiseする経路はvoidコンテキストで受け入れ可能であるため、unionはマーカーを弱めません。
インラインRBS注釈と推論境界
Section titled “インラインRBS注釈と推論境界”Rigorの「Rigor固有のインライン型構文なし」というゴールは、Rubyコードを人間にとって読みやすく、AI支援編集にとって低ノイズに保つことについてです。それはRigorが既存のRubyエコシステム注釈規約を無視することを意味しません。
RigorはRBSとrbs-inline注釈構文と100%互換であるべきで、インライン注釈の解釈と優先度についてSteep 2.0の挙動に従うべきです。RBSとrbs-inlineはインライン型構文の一次規範です。Steepの実装は挙動が散文で完全に指定されていないところでの二次規範です。TypeScript・PHPStan・Pythonのtypingは欠けた概念のための参考資料のままで、互換性ターゲットではありません。
3つのソースが異なるとき、解決順序は以下のとおりです。
- RBSの散文仕様が勝ちます。
- RBSの散文が扱わないインライン構文の問いについてはrbs-inlineのドキュメンテーションが勝ちます。
- RBSの散文もrbs-inlineのドキュメンテーションも挙動を指定していないときにのみSteep 2.0の挙動が勝ちます。
Steepが優先度の高いソースから逸脱する場所では、Rigorは優先度の高いソースに従い、逸脱を文書化された挙動として扱います。そのようなケースは、Steepから移行するユーザーが診断を通じて違いを発見するのではなく違いを見るよう、docs/types.mdで個別に呼び出されるべきです。
Rigorは既存のrbs-inlineとSteep互換の注釈を公式の型ソースとして読むべきです。それらを書き直したり、複雑であるという理由だけで警告したり、# rbs_inline: enabledを要求したりすべきではありません。# rbs_inline: enabledと# rbs_inline: disabledのようなrbs-inline設定ディレクティブのみが無視されます。rbs-inline注釈コメント自体(例えば#: String・# @rbs・引数注釈)は、存在するときは常にパースされ型ソースとして使われます。
スタンドアロンの.rbsファイルと生成されたスタブは、完全な型定義のために好まれる場所のままです。インライン注釈はそれにもかかわらず、存在するとき実際の契約です。それらは単なるヒントではありません。
契約チェックは契約がどこから来たかから独立しています。インライン#: voidとして書かれた戻り値型・# @rbsで書かれたメソッド型・rbs-inlineスタイルで書かれた引数型・生成されたスタブ・外部の.rbs宣言はすべて、同じ方法で実装を制約します。Rigorはメソッド本体が任意の受け付けられたシグネチャソースに矛盾するとき、実装側の診断を報告すべきです。
推奨レベル。Rigorのスタイルガイダンスは作者が.rbソースに型を書くべきかどうかについてのみです。
#: voidと#: botは、意図を表現し有用な推論境界を作るとき強く推奨されます。#: bool・#: String・#: Userのような短い戻り値は中立です。意図をより明確にするとき作者はそれらを書いてかまいません。- union・ジェネリクス・レコード・ネストされたメソッド型のような複雑なインライン型は、有効なRBS/rbs-inline入力で受け入れられなければなりません。Rigorのスタイルガイダンスはそれらを
.rbsまたは生成されたスタブに移すことを好みますが、Rigorはそれらを使用することだけで診断を報告すべきではありません。
推論境界契約。戻り契約が任意の受け付けられたシグネチャソースから利用可能なとき、呼び出し元はその宣言された戻り値を使い、Rigorはメソッド境界で再帰的な戻り値推論を停止できます。実装本体は依然として契約に対してチェックされます。本体が宣言された戻り値の外の値を返せるなら、Rigorは実装側の診断を報告します。
この境界は、深い・再帰的・高価なメソッドにとって特に価値があります。作者がすでに戻り契約を供給したとき、解析がメソッド本体に拡散するのを防ぎます。
シグネチャ内のbottom型。bot戻り契約は、呼び出しが正常に決して返らないことを意味します。呼び出し元は到達可能性とデッドコード解析のためにそれをbotとして扱います。実装解析が正常な戻り経路を見つけたとき、botがインライン#: bot・# @rbs・生成されたRBS・外部の.rbsから来たかに関係なく、Rigorはメソッド本体に対して診断を報告します。
例。
def print(foo) #: void puts '=====' p foo puts '====='endなぜvoid契約がRubyにとって重要であり得るか。void戻り契約は解析器に、戻り値をvoidとして扱い、最後の式からのより精密な推論された戻り値を伝播しないよう告げます。最後の行は依然としてRubyの値(暗黙のreturn)ですが、型契約は「型付けにとって意味のある戻り値なし」で、RBSのvoidの意味と一致します。.rb内に#: voidを書くことは、そのインラインマーカーが作者の副作用のみの意図をより明確にするとき強く推奨されますが、静的な意味は他の任意の受け付けられたシグネチャソースからのvoidと同じです。
実行時の暗黙returnとの相互作用。Rubyの最後の式returnは、値がVMにほぼ常に存在することを意味します。Rigorの義務は実行時の値が決して観測されないことの証明ではなく、静的(値コンテキスト・代入・チェーン・境界挙動)のままです。
botとの関係。bot実装はvoid戻り契約を満たします。なぜなら通常の値が生成されないからです。void結果はbot戻り契約を満たしません。なぜなら呼び出しは依然として実行時に正常に返ることがあるからです。
値コンテキスト復旧。void結果が代入・チェーン・補間・引数として渡される・他の方法で値として使われるとき、Rigorは主要な「voidの値の使用」診断を報告し、それからtopで復旧します。「top上のメソッド」のような、その復旧によってのみ引き起こされる即時の後続診断は、カスケード診断が明示的に要求されない限り、同じ式に対して抑制されるべきです。
インポートされたRBSスロット。既存のRBSはEnumerator[Elem, void]や意図的に値が無視されるブロック引数のような、ジェネリックまたはcallbackスロットにvoidを置けます。Rigorは互換性のためにこれらのシグネチャを保ちます。置換がそのようなスロットを値生成位置に現れさせるなら、結果は通常の値セット型としてではなくvoid結果マーカーとして処理されます。
対話的推論カットオフ(現在の足場ではなくターゲット挙動)。このサブセクションは、解析器が対話的サーフェスを出荷したときの意図されるCLIの挙動を記述します。現在の足場はまだこれらのプロンプトを実装していません。記述はすでに存在する機能のチェックリストではなく、ターゲット製品にとって規範的です。一部のメソッドは実装のみから推論する価値がありません。制約のない演算子を伴う再帰的コードが最も明確なケースです。
def tarai(x, y, z) if x <= y y else tarai( tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y) ) endend多くのRubyクラスが<=と-を実装しているため、引数または戻り契約なしではこのメソッドはユニークで有用な推論ドメインを持ちません。再帰呼び出しもまた戻り値推論を拡散させます。Rigorは演算子の曖昧さと再帰が予算を超えるとき早期に停止すべきです。非対話モードでは不完全推論診断を報告し、境界契約を追加することを示唆します。対話的CLIモードでは、戻り値のみのカットオフのための#: Integer・完全な# @rbsメソッド型・外部の.rbs宣言のような、互換性のある型ソースをユーザーに尋ねてかまいません。選ばれた契約は呼び出し元に信頼され、他の任意の受け付けられたシグネチャソースのように実装に対してチェックされます。
初期予算カテゴリーは、カットオフが予測可能であるよう明示的です。
- 同じメソッドまたは相互再帰クラスタ上の再帰深度。
- 本体が契約のない多くの被呼び出し先に拡散するときのコールグラフ拡張幅。
- 引数感受性ディスパッチのオーバーロード候補数。
<=や-のような演算子が多くのレシーバー型を受け入れるときの呼び出しごとの演算子の曖昧さ。- ジョインされた推論された戻り値のunionサイズ。
- 能力サマリーが新しいメンバーを取得し続けるときの構造的要件成長。
各予算は、捏造された精密な型ではなく理由付きの不完全推論結果を生成します。これは推論を「Rubyコード内のRigor固有のインライン型構文なし」というゴールと互換に保ちます。すなわちユーザーはRigor専用のDSLではなく、受け付けられたRBS形の契約でカットオフを解決します。
すべての予算カテゴリーは、解析器によって強制された健全な範囲とともに、.rigor.ymlの単一のbudgets:名前空間下で設定可能です。受け付けられた範囲外の値は、黙った受け入れではなく設定診断を生成します。最初の実装のデフォルトと範囲は以下のとおりです。
| カテゴリー | デフォルト | 受け入れ範囲 |
|---|---|---|
| 再帰深度 | 5 | 1–32 |
| コールグラフ拡張幅 | 16 | 1–256 |
| オーバーロード候補数 | 8 | 1–64 |
| 呼び出しごとの演算子の曖昧さ | 4 | 1–32 |
| 推論された戻り値のunionサイズ | 24 | 4–256 |
| 構造的要件成長 | 16 | 1–256 |
| 名前付きインターフェイス候補マッチ | 8 | 1–64 |
同じbudgets:名前空間はhashシェイプ消去キー予算(デフォルト16)と値予算(デフォルト8)も運びます。Hashシェイプ消去を参照。差と補集合の診断のための否定的ファクト表示予算はデフォルトで3つの保持された除外です。型演算子は暫定的を参照。
診断識別子・表示・抑制
Section titled “診断識別子・表示・抑制”診断は階層的識別子を使うため、プラグイン作者・RBSメタデータ・ユーザーの抑制マーカーは内部の番号付けと衝突せずにそれらを扱えます。最初の実装のプレフィックスは以下のとおりです。
| プレフィックス | 用途 |
|---|---|
dynamic.* | untypedとDynamic[T]境界横断・チェックされないジェネリック漏れ・証明が動的起源に依存するメソッド呼び出し |
static.* | 不完全推論カットオフを含む、証明に至らない静的チェック |
flow.* | 制御フローナローイング失敗・等価性と述語精緻化の問題・ファクト安定性違反 |
compat.* | RBS・rbs-inline・Steep互換シグネチャ互換性 |
rbs_extended.* | RBS::Extendedペイロードの妥当性・バージョン互換性・衝突レポート |
plugin.<plugin-id>.* | ADR-2で既に指定されたプラグイン貢献の診断 |
generated.<provider>.* | 生成されたシグネチャプロバイダ診断 |
識別子はメジャーバージョン内で安定です。新しい診断は任意のプレフィックス下で追加されてかまいません。リネームまたは除去は非推奨期間を必要とする破壊的変更です。
Dynamic[T]のprovenanceは保守的に表示されます。通常の診断は、ラップされたDynamic[T]形式ではなく、絞り込まれた静的ファセットTを小さなfrom untyped provenance注付きで表示します。dynamic.*カテゴリーの診断とrigor explainを通じて要求された説明は、動的起源が実際に重要な点で見えるよう、完全なDynamic[T]形式を表示します。内部トレースとキャッシュキーは表示規則に関係なく常にラップされた形式を保持します。
抑制マーカーは、エコシステムの移行とクリーンなRigorネイティブ形式のバランスを取るために3つのファミリーで認識されます。
# steep:ignoreのようなSteepスタイルのマーカーはデフォルトで認識されます。Rigorはそれらを独自の診断抑制にマップするため、既存のSteep使用プロジェクトは抑制コメントを書き直さずにRigorを採用できます。マッピングは意図的に保守的です。すなわち行スコープのSteepマーカーのみが受け入れられ、Steepのマーカー文法のいかなるものもRigor固有の設定として再解釈されません。- Sorbetスタイルのファイルレベルマーカー(
# typed:)とRuboCopスタイルの抑制コメントはオプトインです。プロジェクトは.rigor.ymlのcompat.sorbet_ignoreとcompat.rubocop_disableスイッチを通じてそれらを有効にします。デフォルトでオンにすると、Sorbetの型付きモードポリシーとRuboCopのリント対象がRigorの診断抑制と混同されてしまいます。 - Rigorネイティブマーカーは、アプリケーション側の型DSLを発明することなくPHPStanの注釈フィールをミラーするRubyコメント文法を使います。行形式は
# rigor:ignore[<diagnostic.id>]です。ブロック形式は# rigor:ignore-start[<diagnostic.id>]と# rigor:ignore-endです。診断識別子リストは上で定義されたプレフィックスを使います。
未知の診断識別子に名前付けるマーカーは、デッドな抑制が見えるよう警告を生成します。識別子リストのないマーカーはデフォルトで診断です。厳格モードはそれらを完全に拒否します。
RBSのコンテキスト規則は保たれる
Section titled “RBSのコンテキスト規則は保たれる”self・instance・class・voidはRBSでコンテキスト制限を持ちます。Rigorはより内部的なリッチなコンテキスト情報を運んでかまいませんが、エクスポートされたRBSはそれらの制限に従わなければなりません。
リファインメントは内部的である
Section titled “リファインメントは内部的である”Rigorは空でない文字列・正の整数・リテラルセット・真偽値で絞り込まれた型・hash/オブジェクトシェイプのような精緻化された型を推論できます。これらのリファインメントは診断とフロー解析を改善しますが、通常のRBSに消去されます。
数値リファインメントのスコープ
Section titled “数値リファインメントのスコープ”初期のスカラーリファインメントサーフェスは意図的にIntegerで止まります。予約された名前positive-int・negative-int・non-positive-int・non-negative-int・non-zero-intは厳密な整数リファインメントであり、任意のNumeric値の汎用符号リファインメントではありません。
Floatは保守的な規則を保ちます。すなわち等価性と網羅性ナローイングはデフォルトで拒否されます。比較は関係的ファクトを生成してかまいませんが、浮動小数固有の値リファインメントはNaNを除外し、無限大と符号付きゼロを明示的に処理する証明を要求します。将来のfinite-floatまたは非NaN述語はより狭いファクトを解除できるかもしれません。Rationalは最終的に厳密な順序付きリファインメントを受け取ってかまいませんが、それらのリファインメントはRational固有でなければなりません。RigorはRational符号ファクトを整数範囲ファクトとして黙って扱うべきではありません。Complexは、Rubyが複素数の全順序を提供しないため、肯定的・否定的・区間リファインメントに参加しません。有用なComplexファクトは明示的な述語またはプラグイン/RBS効果を必要とします。- リファインメントは
coerce境界を自動的に通過しません。混合数値操作はRubyのディスパッチと関連するRBSまたはプラグインシグネチャに従います。Rigorが操作と昇格経路を証明できないなら、関係的または動的起源ファクトを保ち、保守的な名前的結果に広げます。
したがって非整数の数値精度は、名前的数値境界を越える*-intリファインメントの黙った昇格を通じてではなく、将来の組み込み・信頼できる述語・プラグインおよびRBS効果を通じてオプトインです。
プラグイン前推論サーフェス
Section titled “プラグイン前推論サーフェス”多くのRubyコードベースは、プラグイン・生成されたスタブ・RBSファイルがシェイプを埋めるまでDynamic[top]が支配的です。プラグイン前のサーフェスも依然として有用なファクトを生成すべきです。
- 安定したRubyガードは、Rubyのセマンティクスまたは既存のシグネチャが静的ファセットを正当化するときはいつでも、
Dynamic[top]をDynamic[T]に絞り込みます。初期の有用なチェックには、nil可能性・真偽値性・is_a?・kind_of?・instance_of?・信頼できる組み込みドメインのリテラル等価性・respond_to?メンバー存在ファクトが含まれます。 - 素の
Dynamic[top]上のメソッド呼び出しは、漸進的コードを漸進的に解析できるよう、デフォルトで許されたままです。それらは追跡可能で、厳格モードはコア関係を変えずにそれらを動的から精密への横断として報告してかまいません。 - 診断は、プラグインがロードされていなくても、
Dynamic[T]が欠けたシグネチャ・明示的なuntyped・解析器またはプラグインの制限から来たかを説明すべきです。
これは、フレームワークまたはライブラリ固有のプラグインが出荷される前の解析器のベースライン(baseline)です。プラグイン固有の挙動はADR-2に委ねられたままです。
このADRが「v1」を指すとき、それはRigor解析器の最初のユーザー可視製品リリースを意味します。v1は出荷マイルストーンであり、型モデル仕様全体ではありません。このADRとdocs/types.mdに記述された完全な仕様は長期的な解析器にとって規範的です。v1は、最初のリリースが過剰約束しないよう、その仕様の意図的にスコープされたスライス(slice)を出荷します。境界は以下のとおりです。
- 完全な仕様 — ファクト安定性バケット・ケイパビリティロールカタログ・変異サマリーセット・Dynamic[T]代数・型演算子・RBS::Extendedスキーマ — は規範的です。ユーザー可視ナローイングサーフェスがまだ活用していないときでも、内部データ構造はv1に存在してかまいません。
- v1ナローイングサーフェスは、最初のリリースでエンドユーザー向けにオンになっている導出規則のサブセットです。
- v1.1は次のユーザー可視リリースです。データ構造がすでにサポートする規則を使ってナローイングサーフェスを拡張し、v1の挙動が保たれるようフィーチャーフラグの背後にあります。
v1ナローイングサーフェスは以下を出荷します。
nil・true・false・整数と文字列リテラル・信頼できる組み込みドメインに対する等価チェックが生成する有限リテラルunionリファインメントのリテラルナローイング。- 構文レベルガード:
is_a?・kind_of?・instance_of?・nil?・真偽値性・respond_to?・リテラルセットとの等価性・文をまたぐデータフローを必要としないcaseとcase/in形式でのクラスとパターンマッチングナローイング。 - ユーザープラグインを必要とせずに、コアRubyとstdlibの厳選されたサブセットにRBSまたは
RBS::Extendedを使うメソッド呼び出し解決。RBS::Extendedからの生成されたシグネチャは参加してかまいません。 - レシーバーが静的に分かっている呼び出し位置での、
ファクトの安定性と変異効果に記述されたバンドルされた変異サマリーの直接適用。サマリーはバケット無効化をローカルに駆動します。それらの効果の文をまたいだ伝播はv1.1サーフェスです。
v1ナローイングサーフェスはまだ以下を公開しません。
- 文・ジョイン・ループにわたるファクトと変異効果の手続き内伝播。
- メソッド本体からのケイパビリティロール要件の推論(カタログと明示的な適合ディレクティブは利用可能です。「この本体はどの役割を要求するか」を推論することはv1.1です)。
- ADR-2に依存する、プラグイン提供のフロー貢献。
各v1.1サーフェスは、より大きなサーフェスが着地する間にv1リリースが一貫して振る舞うよう、フィーチャーフラグの背後で出荷されます。
インポートされた組み込みはRubyのセマンティクスに従う
Section titled “インポートされた組み込みはRubyのセマンティクスに従う”RigorはPHPStan・TypeScript・Pythonのtypingのアイデアを構文互換性ではなくセマンティック価値で取り入れるべきです。
予約された組み込みリファインメント名は、認識可能でRubyにきれいにマップされるとき、non-empty-string・positive-int・lowercase-string・literal-string・numeric-string・decimal-int-string・non-empty-array[T]のようなkebab-case綴りを使います。ハイフンは意図的です。なぜならRubyの定数やRBSのエイリアス名に現れることができないため、これらの名前は目に見えてRigor予約だからです。
パラメータ化された型関数は、角括弧を伴う1つの標準のlower_snake Rigor綴りを使うべきです。例えば、key_of[T]は、PHPStanスタイルのkey-of<T>とTypeScriptスタイルのkeyof Tの両方を受け入れることより好まれます。型関数は別の型またはリテラルセットを計算・射影・変換します。-はRigorの型構文の差演算子でもあるため、それらはハイフンを避けます。追加の綴りは具体的な移行または可読性の利益を要求すべきです。
RBSの名前はすでに存在するとき標準のままです。botはbottom型です。never・noreturn・never-return・never-returns・no-returnのようなPHPStanエイリアスや、NeverとNoReturnのようなPythonエイリアスは、初期のエイリアスとして追加されるべきではありません。
クラス名文字列型は委ねられます。Rubyはクラスとモジュールのオブジェクトを直接渡せて、RBSはすでにsingleton(C)を持っています。PHPStanのnew的な型操作またはPythonのtype[C]的な射影は将来の候補のままですが、クラス名文字列ではなくRubyのクラスオブジェクトとファクトリーAPIを中心に設計されるべきです。
型演算子は暫定的である
Section titled “型演算子は暫定的である”Rigorは補集合・差・インデックスアクセス・シェイプ射影・場合によっては条件型のセマンティクスをサポートすべきです。最終的な構文は未決定です。
候補~T演算子は、Tを除くすべてのRubyオブジェクトではなく、現在知られているドメイン内でのTの補集合を意味します。
現在知られているドメインは、除外された値から推論されたドメインではなく、左辺のすでに確立された肯定的ドメインです。例えば、v != "foo"はv: StringをString - "foo"に絞り込みますが、v: untypedをString - "foo"に絞り込みません。素のuntypedでは、RigorはDynamic[top]に動的起源関係的ファクトを加えて保ちます。
作業中の表記ポリシーは以下のとおりです。
- CFAが生成する否定的ファクトの簡潔な表示形式として
~Tを使います。 RBS::Extended注釈での差型の好ましい明示的な作成形式としてT - Uを使います。- 実装に
T - UをT & ~Uに正規化することを許します。
保持は有限ドメイン精度と、開いたドメインのための予算に従います。
- 有限ドメインは厳密に正規化します。
"foo" | "bar"から"foo"を引くと"bar"になります。すべての代替を除去するとbotになります。 - 大きなまたは未知のドメインは予算下で否定的ファクトを保ちます。予算が超過されたとき、Rigorは除外が省略されたことを記録し、
Integer - 0 - 1 - 2 - ...のような不安定なチェーンをレンダリングするのではなく、肯定的ドメインにフォールバックします。 - 否定的ファクトは他のフローファクトと同じ安定性規則に従います。すなわち代入・変異・未知呼び出し・yieldされたブロック・プラグイン宣言の無効化はそれらを弱めるか除去するかもしれません。
ユーザーが否定的ファクトをグローバル補集合と読み違えないよう、診断表示はドメイン対応契約に従います。
- 内部正規化は
T & ~Uを使い続けてかまいませんが、診断は現在の肯定的ドメインを見える形でレンダリングすべきです。 - 小さな有限ドメインは正規化された肯定的unionとして表示されます。例えば、
"foo" | "bar" - "foo"は"bar"として表示されます。 - 広い既知のドメインは、ベアな補集合ではなく
String - "foo"やInteger - 0のようなD - Uとして表示されます。 - 複数の保持された除外は、ネストされた差や繰り返しの交差ではなく、
String - ("" | "foo")のような平坦化された差として表示されます。 - ベアな
~Uは、周囲の診断がすでにドメインを述べているとき、コンパクトな分岐ローカル表示にのみ許されます。それ以外ではRigorはD - U・top - U・ドメインに名前付ける散文を好みます。 - 動的起源provenanceはドメイン表示を置き換えません。診断は動的起源注付きで
String - "foo"を表示してかまいません。技術トレースはDynamic[String - "foo"]を表示してかまいません。 - 除外予算が超過されたとき、Rigorは長い不安定なチェーンの代わりに肯定的ドメインに省略注を加えて表示します。
省略契約は十分に具体的で、診断はデフォルトで短く、要求に応じて探索可能なまま留まります。
- デフォルトの表示予算は、上位3つの保持された除外を保ち、より多くが内部的に保持されたとき
+N moreで終わります。選択は、最近のナローイング決定に最も参加した値、それから名前的基底よりリテラル値、それから出力を安定に保つレキシカル順を好みます。 +N more注は、ユーザーが完全な内訳を尋ねられることを知るよう、診断識別子にリンクします。rigor explain <diagnostic-id>(および同等の--explainCLIフラグ)は、PHPStanの解析説明の精神で、すべての保持された除外と超過された予算を含む完全なドメイン差を表示します。完全な形式はまた、推論チェーン全体をレンダリングしたい高ティア診断のためにScopeAPIを通じてプラグインに利用可能です。- デフォルト予算は、他の予算と同じ健全な範囲強制内で、
.rigor.yml(budgets.negative_fact_display)を通じて設定可能です。
RBS::Extendedは注釈ベースのメタデータレイヤーである
Section titled “RBS::Extendedは注釈ベースのメタデータレイヤーである”高度な型は、RBSの%a{...}注釈を使って通常のRBS宣言・メンバー・オーバーロードに取り付けられてかまいません。これは標準のRBSツーリングとの互換性を保ちつつ、RigorにString - ""・~"foo"・String where non_emptyのようなリファインメントを読む場所を与えます。
RBS::Extendedは、予約されたキー名前空間下で通常のRBSの%a{...}注釈の中にRigorのメタデータを運ぶ規約のRigorの名前です。最初のバージョンの予約されたキーはrigor:v1:<directive>ペイロードです。関連のないキー下の他のツールの注釈はRigorによって消費されず、解析を変更されずに通過します。Rigorは消去中に他のツールの%a{...}注釈を決して書き直しません。
標準のディレクティブ形式はバージョン付きのrigor:v1:キーの後にペイロードを続けたものです。スキーマはdocs/types.mdのRBS::Extended Annotationsにあります。ADR-1は設計決定を定義し、ディレクティブ文法のための単一の真実のソースとしてdocs/types.mdを使います。このADRから参照される標準的な述語例は以下のとおりです。
%a{rigor:v1:predicate-if-true value is String}def string?: (untyped value) -> bool述語ターゲットは初期にはRBSの引数名とselfに限定されます。RBSの引数名は_var-name_ ::= /[a-z]\w*/文法を使うため、Rigorはディレクティブ識別子で任意のRubyのSymbol名をエンコードする必要はありません。predicate-if-trueのようなハイフン付きのディレクティブ名は、Rigorが注釈ペイロードからパースするため安全です。他のディレクティブ綴りはdocs/types.mdにあります。
バージョンプレフィックスは互換性契約の一部です。Rigorが生成する注釈はrigor:v1:を使わなければなりません。バージョンなしのrigor:ディレクティブは、v1として黙って扱われるのではなく、現在のところ無効であるべきです。rigor:v2:のようなサポートされていない将来のバージョンは通常のRBSツーリングによって保たれますが、Rigorはノードを解析するときサポートされていないメタデータを報告すべきです。
適合性は暗黙的または明示的にチェックできます。暗黙的な適合性がデフォルトです。すなわち通常の代入・引数渡し・メソッド呼び出しは、関連するインターフェイスまたはケイパビリティロールに対する構造的互換性チェックをトリガーします。作者可視のオプトインは要求されません。さらに、Rigorは明示的な設計アサーションディレクティブを受け入れます。
%a{rigor:v1:conforms-to _RewindableStream}class MyBufferendディレクティブはRigorに、現在のいずれかの呼び出し位置がその要件を行使するかに関係なく、MyBufferが_RewindableStreamを満たすことを検証するよう指示します。クラスが公開契約の一部として構造的インターフェイスを満たすことを保証したいライブラリに有用です。ディレクティブは純粋に追加的です。すなわち暗黙的な適合性のセマンティクスを変えず、すでにインターフェイスを満たすクラスは注釈なしで型検査され続けます。
RBS::Extendedメタデータが通常のRBSシグネチャと衝突する場合、Rigorは診断を報告すべきです。
同じノード上の複数の注釈はディレクティブ種別・ターゲット・フローエッジで組み合わされます。完全な重複はベキ等です。互換性のある効果は合成します。例えば、trueエッジとfalseエッジの述語注釈は異なる効果スロットを占めます。衝突は常に診断であり、決して最初が勝つ・最後が勝つではありません。これには互換性のないペイロード構文・同じノード上の互換性のないバージョン・同じ効果スロットに対する2つの非同一なシングルトンディレクティブ・交差がbotである矛盾するリファインメント・通常のRBSシグネチャを超えるリファインメント・「pure」効果がレシーバー変異効果と組み合わされたような両方ともtrueにできない効果宣言が含まれます。
型ガードとアサーション効果は、通常の戻り値型ではなくフロー効果としてモデル化されるべきです。これは、TypeScriptスタイルのナローイング・PHPStanスタイルのアサーション挙動・PythonのTypeGuard/TypeIsスタイルの述語を依然として許しつつ、シグネチャをRBS互換に保ちます。
ADR-1はフロー効果バンドルのセマンティックスキーマを所有します。すなわちフィールドセット・ターゲットパスの意味・確信度規則・効果がスコープをどう変えるかです。ADR-2はそれらのバンドルの拡張APIパッケージング・登録・サービスの寿命・プラグインprovenanceを所有します。docs/types.mdの製品仕様は、両方のADRが参照すべき詳細な規範的テーブルです。
消去は保守的でなければならない
Section titled “消去は保守的でなければならない”TがRigor型で、erase(T)が生成されたRBS型なら、Tが受け入れるすべての値はerase(T)が受け入れなければなりません。
消去は精度を失うことができます。内部型より狭くなってはいけません。
Hashシェイプ消去
Section titled “Hashシェイプ消去”HashシェイプはRBSのレコードとHash[K, V]が表現できるよりも多くの情報を運びます。Rigorの消去規則はRBSが綴れるものを保ち、できないとき決定的にフォールバックします。詳細なアルゴリズムはdocs/types.mdにあります。戦略的決定は以下のとおりです。
- 厳密なclosedシェイプは、すべてのキーがRBSレコード構文で表現できるとき、RBSレコードに消去されます。必須エントリーは必須レコードフィールドになり、オプショナルエントリーは綴れるときオプショナルフィールドになり、値は再帰的に消去します。
- オプショナルキーの不在は保存された
nilではありません。Rigorは、キーがオプショナルであるという理由だけで値型にnilを加えてはいけません。 - 読み取り専用・provenance・安定性・キー存在・open/closedマーカーは消去されます。それらを失うことは厳格エクスポートまたは説明モードで報告可能です。
- レコードとして厳密に表現できないシェイプは
Hash[K, V]に消去されます。 Kは既知のリテラルキーのunion・リテラルunionがエクスポート予算を超えるときの広げられた名前的キークラス・openシェイプの追加キー境界です。静的に未知の追加キーはtopを使います。動的起源の追加キーはuntypedを使います。Vは既知の必須値・既知のオプショナル値・openシェイプの追加値境界のunionです。静的に未知の追加値はtopを使います。動的起源の追加値はuntypedを使います。- openシェイプについて、追加値境界は現在観測された値unionに勝ちます。Rigorは、すべての将来の追加キーがすでに見えたエントリーのみから取られた値を持つと推論しません。
- 厳密な空のclosedレコードは、ターゲットRBS出力がサポートするとき
{}に消去されます。それ以外では保守的なフォールバックは「エントリー可能なし」のファクトを保つHash[bot, bot]です。
hashキーは値より識別子的な意味を運ぶため、キーと値のエクスポート予算は別々に設定されます。最初の実装のデフォルトは、リテラルキーunionに対して16、リテラル値unionに対して8で、両方とも.rigor.yml(budgets.hash_erasure_keys・budgets.hash_erasure_values)を通じて設定可能です。予算が超過されたとき、対応する軸は最も近い名前的基底に広がり、もう一方の軸はまだ収まるならリテラルunionのままです。
結果の型仕様からのフィードバック
Section titled “結果の型仕様からのフィードバック”docs/types.mdを理想的な型モデルとして再構築すると、このADRが前へ運ぶべきいくつかの要件が追加されます。
- 構造的型付け(structural typing)は明示的だが限定的であるべきです。RBSのクラスとモジュールは名前的のままです。RBSインターフェイスとRigorのオブジェクトシェイプはRubyのダックタイピングのための橋渡しです。
- IO的な互換性は、関連のない名前的クラスを部分型として扱うことや、すべての呼び出し位置でアドホックなunionを要求することではなく、推論されたケイパビリティロールを通じてモデル化されるべきです。
- オブジェクトシェイプファクトはメンバー種別・呼び出しシグネチャ・可視性・provenance・安定性・確信度を必要とします。
respond_to?ガードはメンバー存在を証明できますが、完全なインターフェイス互換性を証明するには十分ではありません。 - 型エンジンは式エッジスコープを必要とします。各式は、短絡条件がオペランド間でファクトを更新できるよう、normal・truthy・falsey・exceptional・到達不能な出力スコープを生成できるべきです。
- 否定的型と差型は現在ドメインモデルを必要とします。
String | Symbol内の~"foo"は、現在ドメインがtopでない限り、グローバルなtop - "foo"と同じではありません。 - 等価ナローイングはRubyのディスパッチを尊重しなければなりません。Rigorは組み込み・RBS効果・プラグインのための信頼できる等価ファクトを必要とします。それ以外では
==が同一性であるかのように黙ってふりをするのではなく、関係的ファクトを保つべきです。 - 漸進的ファクトはprovenanceを必要とします。
untyped値を絞り込むことは分岐内で有用であり得ますが、診断・ジェネリックスロット・ジョインは依然として値がチェックされない境界を横断したことを知っているべきです。作業中の内部形式はDynamic[T]で、素のuntypedはDynamic[top]として表現されます。 - シェイプ・メンバー・hashキーファクトは無効化規則を必要とします。代入・変異・未知呼び出し・yieldされたブロック・プラグイン宣言の効果はファクトを弱めるか除去するかもしれません。
- RBS消去はプレゼンテーション層ではなく、型設計の一部です。すべての内部リファインメント・関係・provenanceマーカーは保守的な消去規則を必要とします。
拒絶された・委ねられた候補決定
Section titled “拒絶された・委ねられた候補決定”このADRは、議論されたが現在の方向として受け入れられなかった候補のアイデアの明示的な注を保ちます。
| 候補 | 状態 | 理由 |
|---|---|---|
untypedをtopの別名として扱う | 拒絶 | untypedは漸進的境界と精度の損失を表します。topは最大の静的値型です。それらを縮退すると診断とprovenanceが失われます。 |
漸進的一貫性をA ~ Bとして書く | 拒絶 | ~T形式は否定または補集合の型のために予約されているため、漸進的一貫性はconsistent(A, B)として書かれます。 |
*.rbs内のフリーフォーム# @rigor ...コメント | 拒絶 | RBSの%a{...}注釈はRBS ASTにパースされて宣言・メンバー・オーバーロードに取り付けられます。フリーフォームコメントは並列の取り付けモデルを要求するでしょう。 |
| 通常の戻り値型として型述語をエンコード | 拒絶 | 述語とアサーションの挙動は、実行時の戻り値ではなくフロー環境を変えます。Rigorはそれらの効果をRBS::Extended注釈を通じて記録します。 |
| 最初のバージョンでの任意の述語ターゲット構文 | 現時点で拒絶 | 初期ターゲットはRBSの引数名とselfに限定されます。シェイプパス・インスタンス変数・ブロック引数は、明示的なパス構文で後で追加できます。 |
class-string・interface-string・trait-string・enum-stringの取り入れ | 委ねる | Rubyはクラスとモジュールのオブジェクトを直接渡せて、RBSはすでにsingleton(C)を持っています。文字列ベースのクラス名はPHPほど中心的ではありません。 |
PHPStanのnew操作をクラス名文字列操作として取り入れる | 委ねる | new的な射影はファクトリーAPIに有用かもしれませんが、クラス名文字列ではなくRubyのクラスオブジェクトを中心に設計されるべきです。 |
never・noreturn・never-return・never-returns・no-returnをbotのエイリアスとして追加する | 現時点で拒絶 | RBSはすでにbotを提供します。エイリアスを追加すると、表現力を改善せずに表記を増やすでしょう。 |
PythonのNeverとNoReturnをbotのエイリアスとして追加する | 現時点で拒絶 | それらは概念的にbotにマップされますが、Rigorは境界でRBSの綴りを標準的に保つべきです。 |
TypeScriptのany・unknown・object・undefined・null・neverの綴りを取り入れる | 拒絶 | RBSはすでにuntyped・top・nil・botを提供します。TypeScriptの名前はJavaScriptの実行時値モデルに結びついています。 |
PythonのAnyとobjectの綴りを取り入れる | 拒絶 | RBSはすでに動的型のためのuntypedと最大の静的型のためのtopを提供します。 |
PythonのProtocol・TypedDict・Annotated・TypeGuard・TypeIs・Final・ClassVar構文を直接取り入れる | 拒絶 | それらの有用なアイデアはRBSのインターフェイス・レコード・%a{...}注釈・フロー効果・別個のシンボルまたはメンバーファクトにマップされます。 |
| すべてのクラス互換性をTypeScriptスタイルの構造的代入として扱う | 拒絶 | RBSのクラスとモジュール名は名前的です。構造的チェックはRBSインターフェイス・オブジェクトシェイプ・明示的なシェイプ的ファクトに属します。 |
| 構造的インターフェイス代入可能性のために明示的なプロトコル継承または登録を要求する | 現時点で拒絶 | Rubyのダックタイピングは、構造的互換性がメンバーから推論できるとき最もうまく動作します。明示的な宣言は依然として検証要求として有用かもしれません。 |
key-of<T>とkeyof Tの両方を受け入れる | 現時点で拒絶 | 互換性エイリアスが具体的な価値を示さない限り、Rigorは1つの標準の型関数の綴り、現在はkey_of[T]を使うべきです。 |
int<1, 10>のようなPHPStanスタイルの整数範囲を取り入れる | 現時点で拒絶 | Rigorは、RubyとRBSの命名により近づくため、Integer[1..10]のような独自の範囲表記を使うべきです。 |
non_empty_stringのような組み込みリファインメント名のlower_snakeエイリアスを追加する | 現時点で拒絶 | ハイフン付きのリファインメント名は意図的にRigorの組み込みのために予約されています。Lower_snake名は通常のRBS型エイリアスのために利用可能なまま留まるべきです。 |
lower-stringをエイリアスとして追加する | 現時点で拒絶 | lowercase-stringは確立された綴りでより明確です。 |
non-falsy-stringまたはtruthy-stringを追加する | 拒絶 | すべてのRubyのString値は真値であるため、これらの型は精度を加えません。 |
empty・empty-scalar・non-empty-scalar・non-empty-mixedのようなPHP真偽値型を取り入れる | 拒絶 | それらはPHPの真偽値モデルに結びついています。Rigorは`false |
list<T>とnon-empty-list<T>を別個のサーフェス型として取り入れる | 現時点で拒絶 | RubyのArray[T]はすでにリスト的なインデックス付けセマンティクスを持っています。non-empty-array[T]は有用な追加リファインメントを提供します。 |
non-decimal-int-stringを名前付き組み込みとして追加する | 現時点で拒絶 | 別の組み込み名を追加せずにString - decimal-int-stringとして表現できます。 |
Exclude・Extract・NonNullableをサーフェスエイリアスとして追加する | 現時点で拒絶 | RigorはそれらをT - U・T & U・T - nilとして直接表現できます。 |
Partial・Required・Readonly・Pick・Omit・Record・Parameters・ReturnType・InstanceTypeのようなTypeScriptユーティリティまたはmapped型エイリアスを追加する | 現時点で拒絶 | これらは有用な参考設計ですが、Rigorはまずより小さなRuby/RBS形のシェイプファクトと型関数を公開すべきです。 |
標準の条件型構文としてTypeScript構文T extends U ? X : Yを使う | 現時点で拒絶 | Rigorは、型言語の残りに合わない限り、TypeScript構文のコピーを避けるべきです。現在の候補はif T <: U then X else Yです。 |
肯定的:
- RigorはRBSと互換性を保ちつつ精密な診断を生成できます。
- 生成されたRBSは既存のRBS対応ツールによって消費できます。
untyped・top・bot・voidは内部的に区別された意味を保持します。- PHPStan・TypeScript・Pythonスタイルのフロー解析がコア設計の一部になります。
- 高度なライブラリファクトはRubyのアプリケーションコードを変更せずに
.rbs注釈に追加できます。 - 将来のプラグインは新しいユーザー向けの構文を要求せずに精密なファクトを貢献できます。
否定的:
- 型エンジンはRBS ASTの直接ラッパー以上のものを必要とします。
- RBSエクスポートは精度損失処理を必要とします。
- ドキュメンテーションは、なぜRigorがエクスポートできるよりも多くを推論するかを明確に説明しなければなりません。
RBS::Extendedは注意深い注釈ペイロード文法と衝突規則を必要とします。- 否定的型と補集合型はドメイン対応の正規化を必要とします。
オープン課題
Section titled “オープン課題”- MVPのunion/no-method診断後にどのRigor専用リファインメントを最初に実装すべきか?
- 最初の実装ではユーザー作成の
RBS::Extended注釈で~TとT - U表記のうちどれだけを受け入れるべきか? non-empty-stringと整数範囲を超えて、最初のパーサマイルストーンでどの取り入れた組み込みリファインメントを受け入れるべきか?- 述語ターゲットは
parameter-nameとselfを超えてどれだけ早く成長すべきか? - Pythonの
TypedDictに触発されたシェイプファクト(読み取り専用キーやopen/closed追加キーポリシーなど)のうちどれを最初に出荷すべきか? - Rigorは最初のシグネチャメタデータ文法でfinalityと読み取り専用メンバーファクトを値型と別個にモデル化すべきか?
- ユーザーが注釈付きエクスポートを要求するとき、生成されたRBSは消去されたリファインメントを説明する
RBS::Extended注釈を保つべきか? - どの動的起源ソースが明示的なユーザー意図・欠けたシグネチャ・解析器の制限・プラグイン宣言の動的挙動として分類されるべきか?
- フレームワーク固有のオブジェクトシェイプと動的メソッド解決のためにどのプラグインAPIが必要か?
- カスタム等価効果を宣言する正確な
RBS::Extendedまたはプラグインペイロードは何か? - ケイパビリティ要件サマリーは、編集と依存シグネチャ変更にわたってどのキャッシュキーと無効化規則を使うべきか?
- 対話的CLIプロンプトは、インライン
#:・完全な# @rbs・生成されたスタブ・外部.rbsの永続化ターゲットの間でどのように選択すべきか? - 最初の実装で
Dynamic[T]スロットの特別な処理を要求するジェネリック分散ケースはどれか? - 浮動小数リファインメントが後で導入されるなら、有限・非
NaN・符号付きゼロの挙動を証明する正確な信頼できる述語または効果ペイロードは何か? - ブロック呼び出しタイミング・クロージャエスケープ・レシーバーまたは引数の変異・読み取り専用/純粋挙動をエンコードする正確な効果ペイロードは何か?
- どの非述語の
rigor:v1:ディレクティブを最初に標準化すべきで、どれがプラグイン専用メタデータのまま留まるべきか? - 整数リファインメントマイルストーン後に、もしあるとすれば、どの非整数の数値リファインメント名を受け入れるべきか?
- Rigorは構造的インターフェイスとオブジェクトシェイプチェックでRubyのprotected呼び出しレシーバー制限を正確にどうモデル化すべきか?
現在のドラフト仕様はdocs/types.mdで保守されています。
背景となる研究ノート
Section titled “背景となる研究ノート”本ADRで決定した名前的型優先 / RBSスーパーセットの立場を方向づけた(または事後的に正当化する)外部文献レビュー:
docs/notes/20260518-matsumoto-2008-poly-records-rigor-review.md— 松本&南出2008のRuby向けGarrigueカインド付き多相レコード型推論。この論文は、RBSが名前的な事前宣言を要求することで回避している失敗モード(Array/String/Integerでの多相再帰の崩壊、可変長引数の非対応、mapが非正則になること、推論型が約57k個の束縛型変数まで膨張すること)を露呈する。Rigorが拒否した構造的レコード優先のパスに対する負の実験として読める。docs/notes/20260518-matsumoto-2010-cfa-rigor-review.md— 松本&南出2010のSemiRubyに対するセミフローセンシティブなCFA。メソッド定義のフローセンシティブ性(flow-sensitive)(値のフローセンシティブ性なし)が、それ自体として首尾一貫した設計選択であることを示す——Rigorの静的ディスパッチャー層は、これをキャッシュ親和性とRactor隔離と引き換えに近似している。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.