コンテンツにスキップ

RBS::Extendedアノテーション

Rigorは仮称RBS::Extendedの下で*.rbsファイルのRBSアノテーションからRigor固有のメタデータを読み取る場合があります(MAY)。

RBSはすでに宣言、メンバー、メソッドオーバーロードに対して%a{...}アノテーションをサポートしています。RBS::Extendedは予約済みキー名前空間の下でそれらのアノテーションにRigorメタデータを添付する慣習のRigorの名称です;最初のバージョンはrigor:v1:<directive>ペイロードを予約します。関係のないキーを使う同じノード上のアノテーションは他のツールに属し、Rigorによって消費されません。Rigorは解析と消去中にそれらを変更せずに保存しなければなりません(MUST)。

これらのアノテーションにより、ユーザーとプラグイン著者はRubyアプリケーションコードを変更せず、通常のRBSパーサを壊すことなく標準RBSを超える型を記述できます。標準RBSツールはこれらのアノテーションを保存または無視できなければなりません(MUST)。これはPythonのAnnotated[T, metadata]と同じ互換性原則に従います: 基本型はメタデータを理解しないツールにとっても意味があり続けます。

%a{rigor:v1:return: non-empty-string}
def read_name: () -> String
%a{rigor:v1:param: value is non-empty-string}
def normalize: (String value) -> String
%a{rigor:v1:assert value is non-empty-string}
def assert_present!: (String value) -> void
%a{rigor:v1:assert-if-true value is "foo"}
%a{rigor:v1:assert-if-false value is ~"foo"}
def check: (untyped value) -> bool

return:param:assert*predicate-if-*の右辺は、RBSスタイルのクラス名(String::Foo::Bar)またはインポート済み組み込みカタログ(imported-built-in-types.md)のkebab-caseリファインメント(refinement、篩型とも)ペイロードのいずれかを受け付けます。リファインメントペイロードはBuiltins::ImportedRefinements::Parserを通じてパラメータ化形式non-empty-array[Integer]non-empty-hash[Symbol, Integer]int<min, max>をサポートします。型引数位置はSymbolおよびStringリテラルトークン(:name / "name")と、それらの|によるユニオン(union、合併型とも)(:a | :b | "c")も受け付けます;パーサは各リテラルをConstant<value>に持ち上げ、ユニオンをType::Combinator.unionを介して畳み込むため、pick_of[T, :a | :b]やプラグイン提供のPick[T, "name" | "email"]のようなシェイプ(shape)射影ヘッドが合成ASTのワークアラウンドなしに端から端まで通ります。クラス名ディレクティブは~T否定を使う場合があります(MAY);リファインメント形式のディレクティブは現在使ってはなりません(MUST NOT)(差分対リファインメントの代数は将来のスライス(slice)のために予約されています)。

  • 通常のRBSシグネチャは互換性契約(contract)のままです。
  • RBS::ExtendedアノテーションはRigorのためにその契約を絞り込むか説明します。
  • アノテーションキーはバージョン管理されたrigor:v1:名前空間を使います(例: rigor:v1:returnまたはrigor:v1:predicate-if-true)。
  • アノテーションキーが最初に来ます;残りのテキストはRigor固有のペイロードです。
  • Rigor生成アノテーションは明示的なrigor:v1:プレフィックスを使わなければなりません(MUST)。バージョン管理されていないrigor:ディレクティブは発行してはなりません(MUST NOT)し、互換性移行の必要がない限り無効として扱うべきです(SHOULD)。
  • バージョンプレフィックスはディレクティブIDの一部です。Rigor v1はrigor:v1:ディレクティブのみを読み取ります;サポートされていないrigor:vN:ディレクティブはRBSツールによって保存されますが、Rigorが解析するノードにある場合はサポートされていないメタデータとして報告されます。
  • 同じRBSノード上の複数のアノテーションはソース順序に依存せず決定論的に独立して解釈されなければなりません(MUST)。
  • 正確に重複するアノテーションは冪等です。
  • 互換性のあるアノテーションはディレクティブの種類、ターゲット、フローエッジによって合成されます。例えば、同じパラメータの真エッジと偽エッジの述語ファクト(fact)は異なる効果スロットです。
  • 競合するアノテーションは診断です。Rigorは最初優先または最後優先の挙動を使ってはなりません(MUST NOT)。競合は、ペイロード構文の非互換性、同じノードでのバージョンの非互換性、同じ効果スロットに対する2つの非同一シングルトンディレクティブ、積集合がbotである矛盾するリファインメント、通常のRBS契約を超えるリファインメントを含みます。
  • 著者は明示的なユーザー著作差分型にはT - Uを優先し、負のファクトとコンパクトな診断表示には主に~Tを使うべきです(SHOULD)(type-operators.md参照)。
  • アノテーションがRBSシグネチャと競合する場合、Rigorは診断を報告しなければなりません(MUST)。
  • エクスポートされたプレーンRBSは、ユーザーが保存を要求しない限りRigorのみのアノテーションを削除または消去しなければなりません(MUST)。
  • アノテーション文法はバージョン管理されており、実装経験がそれを証明するまで小さくあるべきです(SHOULD)。非互換な文法変更はrigor:v1:のセマンティクスを変更するのではなく新しいバージョンプレフィックスを必要とします。

RigorはPythonのTypeGuard/TypeIsスタイルの述語、TypeScriptスタイルの型ガード、PHPStanスタイルのアサーションをRBSメソッドシグネチャに添付されたフロー効果としてモデル化します。

%a{rigor:v1:predicate-if-true value is String}
%a{rigor:v1:predicate-if-false value is ~String}
def string?: (untyped value) -> bool
%a{rigor:v1:predicate-if-true self is LoggedInUser}
def logged_in?: () -> bool
%a{rigor:v1:assert value is String}
def assert_string!: (untyped value) -> void
%a{rigor:v1:assert-if-true value is String}
def valid_string?: (untyped value) -> bool
ディレクティブ効果
rigor:v1:return: Tすべての呼び出しサイトでRBS宣言の戻り値型をTでオーバーライドします。
rigor:v1:param: name [is] TパラメータnameのRBS宣言型をTに絞り込みます。オーバーロード選択/引数型チェックと推論中のメソッド本体内の両方に適用されます。isグルーワードはオプションです。
rigor:v1:predicate-if-true target is T条件として使われる呼び出しのブランチでtargetTに絞り込みます。
rigor:v1:predicate-if-false target is TブランチでtargetTに絞り込みます。
rigor:v1:assert target is Tメソッドが正常にリターンした後にtargetを絞り込みます。
rigor:v1:assert-if-true target is Tメソッドが真値を返したときにtargetを絞り込みます。
rigor:v1:assert-if-false target is Tメソッドがfalseまたはnilを返したときにtargetを絞り込みます。

真ブランチのみの述語はPythonのTypeGuard的な挙動に十分です。両ブランチを記述する述語ペアはPythonのTypeIs的な挙動に十分です。偽ブランチはより明確な場合は明示的な負の型として書く場合があります(MAY):

%a{rigor:v1:predicate-if-true value is String}
%a{rigor:v1:predicate-if-false value is ~String}
def string?: (untyped value) -> bool

初期ターゲット文法は意図的に小さくなっています:

target ::= parameter-name | self

parameter-nameは任意のRuby Symbolではなく、RBSメソッドパラメータ名を指します。RBSパラメータ名は_var-name_ ::= /[a-z]\w*/に従うため、述語ターゲットは既存の識別子スタイルに従います。predicate-if-trueのようなディレクティブのハイフン付き単語はアノテーションペイロード内にあり、Ruby Symbolとしてではなくリgorによって解析されます。

述語が引数を参照する必要がある場合、RBSメソッド型はその引数に名前を付けなければなりません(MUST):

# 良い例: `value`を参照できます。
%a{rigor:v1:predicate-if-true value is String}
def string?: (untyped value) -> bool
# 述語ターゲットに対して情報が不十分です。
def string?: (untyped) -> bool

将来のバージョンはターゲットをインスタンス変数、レコードキー、シェイプパス、ブロックパラメータに拡張する場合がありますが(MAY)、それらはアノテーションディレクティブ名をオーバーロードするのではなく明示的なパス構文を使うべきです(SHOULD)。

暗黙の構造的適合がデフォルトです。通常の代入、パラメータ渡し、メソッド呼び出しは、著者が見えるオプトインを必要とせずに関連するインターフェースまたはケイパビリティ(capability)ロールに対する構造的互換性チェックをトリガーします(structural-interfaces-and-object-shapes.md参照)。

さらに、明示的な適合ディレクティブにより、クラスがその公開契約の一部として構造的インターフェースを満たすことを宣言できます:

%a{rigor:v1:conforms-to _RewindableStream}
class MyBuffer
end

ディレクティブはRigorに対して、現在の呼び出しサイトがその要件を実行するかどうかに関係なく適合を検証するよう指示します。これは構造的契約を使用から生まれるプロパティではなくチェックされた設計アサーションにしたいライブラリに有用です。同じクラス上の複数のconforms-toディレクティブは許可され、インターフェースの積集合のように結合します。宣言されたconforms-toインターフェースが満たされない場合、Rigorは診断を報告しなければなりません(MUST);満たされたディレクティブはサイレントです。

ディレクティブは純粋に追加的です。暗黙の構造的互換性は引き続き適用され、すでにインターフェースを満たすクラスはアノテーションなしで型チェックを続けます。

このセクションはフロー効果バンドルの正規のセマンティクススキーマです。拡張APIドキュメント(ADR-2以降)はプラグインが貢献をパッケージ化して返す方法を説明するときにこのスキーマを参照しなければなりません(MUST)。

型仕様はスコープの直接ミューテーションではなく、ファクトを公開する拡張APIに依存します。プラグインまたはRBS::Extendedアノテーションは以下を持つフロー効果バンドルを貢献する場合があります(MAY):

  • 正常リターン型;
  • 真値エッジファクト;
  • 偽値エッジファクト;
  • リターン後アサーションファクト;
  • 例外または非リターン効果;
  • ブロック呼び出しタイミング効果;
  • レシーバー、引数、ブロック、キャプチャされたローカルのエスケープ効果;
  • レシーバーと引数のミューテーション効果;
  • ファクト無効化効果;
  • 呼び出しによって導入される動的リフレクションメンバー;
  • 貢献されたファクトと効果のprovenanceと確実性。

解析器はこれらの貢献を組み込みガードに使うのと同じ制御フロー機構を通じて適用します(control-flow-analysis.md参照)。これにより短絡式が精密なままになります。例えば、&&の左辺で使われるプラグイン定義述語は右辺の解析に使われるスコープを絞り込まなければならず(MUST)、その負のファクトは||の右辺に流れなければなりません(MUST)。

貢献のマージは決定論的で解析器が所有します:

  • 貢献はprovenanceを持ちます: コアRubyセマンティクス、受け付けられたシグネチャまたはRBS::Extendedアノテーション、生成されたメタデータ、プラグインのどこから来たかを含みます。
  • コアRubyセマンティクスと受け付けられたシグネチャ契約は権威があります。RBS::Extended、生成されたメタデータ、プラグインは互換性のあるファクトを絞り込む場合がありますが(MAY)、通常のRuby/RBS契約を弱体化または矛盾させてはなりません(MUST NOT)。
  • 同じターゲット、フローエッジ、効果の種類の互換性のあるファクトは合成されます。正の型ファクトは積集合を取り、負のファクトと関係的ファクトは通常のバジェットの下で蓄積され、ミューテーション、エスケープ、無効化効果は保守的に結合されます。
  • 矛盾する貢献は診断です。最初優先または最後優先の挙動ではありません。Rigorは最も近い非競合権威ファクトを保持し、そのターゲットとエッジの競合する貢献を無視または弱体化すべきです(SHOULD)。
  • 真値エッジと偽値エッジのファクトはエッジローカルのままです。プラグインは片側のファクトを貢献する場合がありますが(MAY)、貢献が明示的に提供するか、コア解析器が互換性のあるファクトから導出できない限り、Rigorは反対のエッジを推論してはなりません(MUST NOT)。
  • 動的リターン貢献は選択されたシグネチャまたはデフォルトのリターン契約に対してチェックされます。プラグインは互換性のあるリターンを絞り込む場合がありますが(MAY)、非互換なリターン貢献は契約のオーバーライドではなく競合診断です。
  • maybeの証拠が繰り返されても、単に数によってyesになることはありません。確実性は、貢献がより強い証明を提供するか、コア解析器が互換性のあるファクトからそれを導出できる場合にのみ変わります。

将来のターゲット文法は明確な安定性ルールによってのみ成長すべきです(SHOULD)。妥当なターゲットには以下が含まれます:

  • self;
  • 名前付きパラメータ;
  • 呼び出しサイトで可視のローカル変数;
  • self.nameのようなレシーバーメンバー;
  • @nameのようなインスタンス変数;
  • config[:mode]のようなハッシュまたはレコードキー;
  • リテラルインデックスを持つタプルまたは配列要素;
  • メソッドが純粋または安定していることが既知の場合、同じレシーバーのメソッド結果パス。

解析器の背後でミューテートされる可能性のあるターゲットは、アノテーションで拒否されるか、maybeとして扱われるか、または明示的な安定性メタデータとペアにされるべきです(SHOULD)。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.