コンテンツにスキップ

エラーの読み方

この章はRigorが出荷する診断のカタログ、それらが属するファミリー、そして診断が間違っているとき(または深刻度を変えたいとき)に抑制する方法です。診断に — どちらの方向であれ — 驚かされたときに最初に開くページです。

この章の内容 診断の構造 · ルールカタログcall.* · flow.* · def.* · assert.* · dump.* · 深刻度プロファイル · ルールごとのオーバーライド · 抑制 — インソース · ファイル全体 · プロジェクト全体 · CIのためのベースライン差分 · 期待通り発火しない? · 予期せず発火した? · 導入ワークフロー

lib/user.rb:42:7: error: undefined method `upcas' for "alice" [call.undefined-method]
↑ ↑ ↑
│ │ └─ 修飾ルール
│ └─ メッセージ
└─ 深刻度 (error / warning / info)

修飾ルール(call.undefined-methodflow.always-raisesdef.return-type-mismatchなど)はルールの安定した識別子です。以下で使います:

  • ソース内の# rigor:disable <rule>行末抑制
  • ソース内の# rigor:disable-file <rule>ファイルスコープ抑制
  • .rigor.ymlseverity_overrides:
  • .rigor.ymldisable:

ワイルドカードも使えます — # rigor:disable callはその行のすべてのcall.*ルールを抑制します。

シェルを離れずにルールの内容を調べたい場合は、rigor explain <rule>でルールのサマリー、発火条件、非発火条件、抑制トークン、作成重大度、プロファイルごとの重大度を確認できます。引数なしのrigor explainは出荷済みすべてのルールのインデックスを表示します。

5つのファミリー、それぞれに1つ以上のルール:

メソッド呼び出しの形状が間違っているときに発火します。

ルール発火するときデフォルト深刻度
call.undefined-methodレシーバークラスが静的に既知で、メソッドがそれに定義されていない(RBSまたはインソース)。error
call.wrong-arity位置引数の数がどのオーバーロードのアリティも満たさない。error
call.argument-type-mismatch引数の型がパラメータ契約(contract)(RBSまたはRBS::Extended param:)を証明可能に満たさない。error
call.possible-nil-receiverレシーバー型が`Tnilで、メソッドがNilClass`で定義されていない。
call.unresolved-toplevelトップレベル(どのdef / class / moduleの外側でもない)の暗黙的self呼び出しが、同一ファイルのdefpre_eval:モンキーパッチ、Kernel / Objectのメソッドのいずれにも解決しない — スタンドアロンスクリプトのタイポを顕在化させる。balancedでwarning、strictでerror、lenientで抑制

call.*ルールは実際のコードで最も量の多い診断です。また最も洗練されています — それぞれがRigorが根底にある事実を証明できる場合にのみ発火します。

制御フロー自体が健全(soundness)でないときに発火します。

ルール発火するときデフォルト深刻度
flow.always-raises式のすべての到達可能な評価が例外を投げる(例: n: Integerのときn / 0)。error
flow.unreachable-branchif / unless / 三項演算子の述語が構文的リテラルで、対応する到達不能ブランチが空でない。warning
flow.always-truthy-conditionif / unless / 三項演算子の述語が推論型により証明可能に真値(または偽値)で、ループボディ内と防衛的述語コールに外科的スキップあり。warning
flow.dead-assignment同じdefボディ内で一度も読まれないローカル変数への単純な書き込み。warning

メソッドの本体が宣言された契約に違反するときに発火します。

ルール発火するときデフォルト深刻度
def.return-type-mismatch本体の最後の式の推論された型がRBS宣言の戻り値型を満たせない。%a{rigor:v1:return: <refinement>}オーバーライドを尊重。balancedプロファイルでwarning、strictでerror
def.ivar-write-mismatch同じクラスボディ内で後の@var = ...書き込みの具体クラスが最初の書き込みのクラスと異なる(NilClass-to-clearはアローリスト)。error
def.method-visibility-mismatch明示的レシーバーのコールが、周囲のクラスボディで:privateとして発見されたメソッドを持つNominal[X]をターゲットにする。error
def.override-visibility-reducedオーバーライドが、プロジェクト定義の祖先から継承した可視性を縮小する(public → protected/private、protected → private)。上位型を保持する呼び出し元を壊す。balancedでwarning、strictでerror、lenientで抑制
def.override-return-widenedオーバーライドの宣言された戻り値が、継承した戻り値を広げる(共変性)。両側が著作されたRBSシグネチャを持つ場合の、証明可能な違反でのみ発火する。balancedでwarning、strictでerror、lenientで抑制
def.override-param-narrowedオーバーライドが、継承したパラメータ型を狭める(反変性)。一致する位置パラメータどうしを比較する。両側に著作された単一オーバーロードのRBSシグネチャが必要。balancedでwarning、strictでerror、lenientで抑制

3つのdef.override-*ルールは、プロジェクト定義のクラス/モジュール階層(上位クラスチェーン + include/prependされたモジュール、クロスファイルで解決)をまたいで適用されたリスコフの置換原則のシグネチャ規則です。これらは付録: リスコフの置換の概念的な主題です。

assert.* — ランタイムアサーションルール

Section titled “assert.* — ランタイムアサーションルール”
ルール発火するときデフォルト深刻度
assert.type-mismatchassert_type("expected", value)呼び出しの実際の推論された型が期待文字列と一致しない。error
ルール発火するときデフォルト深刻度
dump.typedump_type(value)が呼ばれた — 推論された型を名前付きのinfo診断として出力する。info

dump_typeはデバッグ時のイントロスペクションプローブです: 疑わしいコードに散りばめて、rigor checkを実行し、診断ストリームから推論された型を読みます。

Rigorは出荷された深刻度を再スタンプする3つの名前付き深刻度プロファイルを提供します:

プロファイル動作
lenientほとんどのルール → warning;不確かなルールはinfoに下がる。レガシーコードのCI向け。
balanced(デフォルト)ほとんどのルール → error; dump.typeinfo。出荷された動作。
strictbalancedでの:warningルールも含め、すべて → error。レガシーノイズのない新しいプロジェクトに適しています。

.rigor.ymlで設定:

severity_profile: strict

単一ルールの深刻度をオーバーライド:

severity_overrides:
call.argument-type-mismatch: warning
def.return-type-mismatch: off

offは診断を結果から完全に除去します — プロファイル全体の設定をほとんどのルールに使いつつ、1つだけ沈黙させたいときに有用です。

ファミリーワイルドカードもオーバーライドで使えます:

severity_overrides:
call: warning # すべてのcall.*ルールを降格
dump: off # すべてのdump.*ルールを除去

ルールごとのエントリーはファミリーワイルドカードエントリーより優先されます:

severity_overrides:
call: warning # すべてのcall.* → warning
call.undefined-method: error # ただしundefined-methodは依然としてerror

YAMLは裸のoffを予約済みにしています。削除された深刻度が適用されないように見える場合は、クォートしてください: "off"onも同様です。

"hello".no_such_method # rigor:disable call.undefined-method

コメントは診断と同じ行になければなりません。修飾ルール、ファミリーワイルドカード、またはallを使います:

"hello".no_such_method # rigor:disable call
"hello".no_such_method # rigor:disable all

複数行のブロックの場合は、各行で抑制します — Rigorはまだdisable-block構文を出荷していません。

ファイル内のルールをすべての箇所でサイレンスする必要がある場合——典型的には生成されたファイル、フィクスチャ、既知の偽陽性を引き起こすベンダーのスニペット——ファイルのどこかに1つの# rigor:disable-fileコメントを置きます:

# rigor:disable-file call.undefined-method
# このファイル全体は生成済みです; 解析器のコールサーフェスは
# これらのスタブのランタイムレイヤーと不一致です。

慣習的にコメントは先頭近くに置きますが、Rigorはファイル内のすべてのコメントをスキャンするため、どこに置いても機能します。同じトークン形式が適用されます: 修飾ルール、ファミリーワイルドカード、またはall。行スコープの# rigor:disable形式は引き続き機能します — 両者は組み合わせて使え、.rigor.ymlのプロジェクト全体のdisable: [...]も引き続き適用されます。

.rigor.yml
disable:
- call.possible-nil-receiver

プロジェクト全体でルールを除去します。severity_overrides: { call.possible-nil-receiver: off }よりも強力なハンマーです — どちらも機能します;選択はスタイルの問題です。

既存のコードベースにRigorを採用する場合、今日すぐには修正しない正当だが既存の診断の長いテールを引き継ぐことがよくあります。実用的な方法は現在の状態をベースライン(baseline)としてスナップショットし、CIがPRによって新たに導入された診断のみで失敗するようにすることです:

Terminal window
# 一度: 現在の診断サーフェスをキャプチャ。
rigor check --format=json > rigor.baseline.json
git add rigor.baseline.json
git commit
# PRごと: コミットされたベースラインと比較。
rigor diff rigor.baseline.json

rigor diffはベースラインになかった各診断の+ NEW行と、解消された各診断の- FIXED行を出力します。新しい診断が現れた場合の終了コードは1、そうでない場合は0です — したがって新しい違反を追加するとCIは失敗しますが、ベースラインに記録されたレガシー診断は問題ありません。

ベースラインの行を修正した場合は、同じrigor check --format=json > rigor.baseline.jsonで再生成して、プロジェクトが単調に厳しくなるようにします。rigor diff自身の--format=json形式もエディタ / ダッシュボード統合のために利用可能です。

rigor diffは軽量でアドホックな形式です — CIスクリプトの中で手作業でdiffするJSONファイルです。ほとんどのプロジェクトは代わりに管理されたベースラインを採用します: rigor baseline generate.rigor-baseline.ymlを書き出し、baseline:設定キーでそれを指定すれば、以降はrigor check自身が記録済みの診断ではクリーンに終了し、新しいものだけを表面化します — 別途のdiffステップは不要です。これはrigor-project-initスキルがセットアップしてくれる道筋です;完全なワークフローはベースラインを(設計はADR-22を)参照してください。

期待していたのに診断が発火しない理由

Section titled “期待していたのに診断が発火しない理由”

最もよくある理由:

  1. レシーバーがDynamic[Top]です。Rigorは漸進的(gradual)レシーバーに対して沈黙します。rigor type-of <file>:<line>:<col>を実行してエンジンが何を見ているか確認します。
  2. メソッドが階層のどこかに存在します。 祖先クラス/モジュールの一致するdefが1つでもあればcall.undefined-methodは沈黙します。
  3. 呼び出しがメソッド本体内の暗黙的selfです。Rigorは暗黙的selfの呼び出しをフラグしません — メタプログラミングの多いコードではノイズが多すぎます。
  4. リテラルは解析器が証明できない方法でランタイムに空/nilの可能性がありますs = ARGV.first; s.upcaseは黙って通ります。なぜならsはランタイムで正当に非空文字列である可能性があり、Rigorは証明できないことをフラグしないからです。明示的なガードまたはparam:の締め付けを追加します。
  5. 対象のルールが設定で無効にされています.rigor.ymlと問題のファイルの# rigor:disableコメントを確認します。
  6. 深刻度プロファイルがそれを降格しましたlenientでは、:warningとして発火するルールがさらに:infoに降格されてCIスクリプトでフィルタアウトされた可能性があります。

疑わしいときは--explainで実行します:

Terminal window
rigor check --explain lib

これにより、エンジンが取ったすべてのfail-softフォールバックごとに:info診断が追加されます — それ以上見えなかったためDynamic[Top]に拡幅した各箇所。現実的なコードでは出力がノイズになりますが、「ここで診断が来ると思っていた」デバッグには非常に価値があります。

来るべきでないと思う診断が発火している理由

Section titled “来るべきでないと思う診断が発火している理由”

ほぼ常に以下のいずれかです:

  1. Rigorが正しいです。 典型的なケース: メソッドのRBSシグがString?と言っているが、プロジェクトのランタイム不変条件が非nilを保証している。シグを修正するか(推奨)、RBS::Extendedreturn:ディレクティブを追加するか、その行に# rigor:disableを追加します。
  2. RBSシグが欠落または間違っています。 クラスが.rbsのないgemに存在するか、プロジェクト自身のsig/がソースと同期していません。シグを更新または追加します。
  3. 定数が間違って参照されています。 定数解決はRBSコアまたはインソースクラス探索にフォールバックする可能性があります;両方が見逃す場合、呼び出しはDynamic[Top]を通り診断は出ませんが、間違ったクラスに対する兄弟呼び出しが発火するかもしれません。
  4. 診断が正真正銘の偽陽性です。 まれです — Rigorの設計優先事項は偽陽性なし — しかし可能性はあります。抽出できる最小の再現コードで問題を報告してください。

Rigorを採用したばかりのプロジェクトでの実用的なループ:

  1. rigor check libを一度実行してベースラインを確認します。
  2. すべての診断をざっと確認します。以下のいずれかとして分類します: a. 実際のバグ。 コードを修正します。 b. 欠落/間違ったRBS。 シグを更新するか新しいものを追加します。 c. 正当なノイズ。 その行に# rigor:disable <rule>を追加するか、.rigor.ymldisable:を追加します。
  3. 再実行します。診断ストリームがきれいになるまで繰り返します。
  4. rigor check libbalancedプロファイル(またはより厳密)の下でCIに追加します。
  5. プロジェクトの不変条件がより証明されるにつれて、# rigor:disable行をRBS::Extendedディレクティブに格上げして、解析器に実際の契約を教えます。

クリーンなrigor checkの実行が目標です;グリーンのCIバッジは「発火するすべての診断は受け入れるものだ」を意味します。

第9章 — プラグインexamples/ディレクトリへの1ページのポインタです。プラグインはプロジェクト固有のDSL(単位、ルートヘルパー、非推奨など)のためにRigorを拡張します。ほとんどのプロジェクトはプラグインを書くことはないでしょう;この章はそのオプションがあることを知っていただくために存在します。第10章 — Sorbetとの共存はSorbetコードベースから来たプロジェクト向けです: rigor-sorbetアダプタがsig { ... }ブロック、RBIファイル、T.let / T.cast / T.must / T.unsafeアサーションを型ソースとして読み取ります。

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