Steep 2.0 cross-check triage (2026-05-03)
Steep 2.0.0をtool/steep/に独立bundleとして追加し、make steep-check
でlib/をsig/に対して走らせた結果のトリアージ。Rigor自身のmake checkは引き続きクリーンに保つ前提で、外部チェッカーの観点からどの警告が
Rigorでも検知すべき真の不一致で、どれがRigorでは拾わないのが
正しい(= Steep側の限界)で、どれがより精密な型付けで偽陽性として
解消できるものかを切り分ける。
- 入力:
make steep-check - 集計: 17ファイル / 54件 (
Ruby::MethodBodyTypeMismatch42件、Ruby::MethodParameterMismatch9件、RBS::DuplicatedMethodDefinition3件) - 分類:
| カテゴリ | 件数 | 概要 |
|---|---|---|
| A. Rigorも検知すべき(真のsig drift) | 48 | 戻り値型の宣言誤り、引数欠落、宣言重複。sig/修正で解消できる。 |
| B. Rigorでは検知しないのが適切(Steep固有の偽陽性) | 0 | 該当なし。今回の警告は全て事実に根拠がある。 |
| C. 適切な型付けで偽陽性として解消可能(Rigorの精密化で消える) | 6 | Array()強制変換とData.defineのキーワード合成を追えていないことに起因。 |
以下、カテゴリ別に内訳と推奨アクションをまとめる。
カテゴリA — Rigorも検知すべき(真のsig drift)
Section titled “カテゴリA — Rigorも検知すべき(真のsig drift)”sig/側の手書き宣言がlib/の実装に追従できていない。Robustness
Principle (docs/type-specification/robustness-principle.md)
の「strict on returns」を素直に適用すれば検出されるべき類のもの。
A-1. 述語メソッドtop / bot / dynamicの戻り値型(39件)
Section titled “A-1. 述語メソッドtop / bot / dynamicの戻り値型(39件)”各タイプキャリア(Top, Bot, Dynamic, Constant, IntegerRange,
Nominal, Singleton, Union, Difference, Refined, Intersection,
Tuple, HashShape)が公開している述語メソッドtop / bot / dynamic
は実装上Trinary.yes/no/maybeを返す(lib/rigor/type/top.rb:26-36)
が、sig/rigor/type.rbs:11-13は
def top: () -> Topdef bot: () -> Botdef dynamic: () -> Dynamicと宣言している。戻り値型の意味そのものが取り違えられている:
sigは「topと呼ぶとTop型インスタンスが返る」と読めるが、実際は
「この型はtopか?」という述語でありTrinaryを返す。
- 検知すべきか: Yes — strict on returnsの素直な違反。Rigorが完成 すれば自動で検出可能。
- 修正: sig側を
def top: () -> Trinary等に揃える。13ファイル × 3 メソッド = 39箇所。 - 影響範囲:
sig/rigor/type.rbsのみ。lib/側は変更不要。
注: Refined#dynamicとDifference#dynamicの警告はbody推定が
(Type::Dynamic | Trinary)になっているが、これも上記と同一原因
(継承先 / 委譲先で誤ってType::Dynamicを返している経路をSteepが
拾った副次効果)なので同じ修正で消える。
A-2. IntegerRange#lower / upperの戻り値(2件)
Section titled “A-2. IntegerRange#lower / upperの戻り値(2件)”lib/rigor/type/integer_range.rb:67-71は
NEG_INFINITY / POS_INFINITYをSymbol番兵で表現しており、lower /
upperはInteger | Float | Symbolを返し得る。一方
sig/rigor/type.rbs:71-72は() -> Numeric。
SymbolはNumericの部分型ではないので不整合。
- 検知すべきか: Yes — 戻り値の集合が宣言より広い、明確なstrict-on-returns違反。
- 修正パスは2通り:
- sigを
() -> (Integer | Float | Symbol)(実体に合わせる) - implを
Float::INFINITY番兵に置換しsigのNumericを維持
- sigを
- どちらが望ましいかはADR 3(型表現)と擦り合わせる必要があるが、現状 Symbol番兵を採用している以上、当面はsig側更新が現実的。
A-3. record_declarationsの引数欠落(1件)
Section titled “A-3. record_declarationsの引数欠落(1件)”lib/rigor/inference/scope_indexer.rb:473 は4引数:
def record_declarations(node, qualified_prefix, identity_table, discovered)sig/rigor/inference.rbs:135は3引数:
def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t] table) -> void第4引数discoveredがsigから落ちている、典型的なsig drift。
- 修正: sigに
Array[untyped] discovered(実型は要確認)を追加。
A-4. RbsLoader#instance_definition / singleton_definitionの重複宣言(3件)
Section titled “A-4. RbsLoader#instance_definition / singleton_definitionの重複宣言(3件)”sig/rigor/environment.rbs:41,48と
同:43,49で同名メソッドが二度宣言され、
戻り値型がuntypedとuntyped?で食い違う:
def instance_definition: (String | Symbol class_name) -> untyped...def instance_definition: (String | Symbol class_name) -> untyped?RBS仕様ではオーバーロード以外の重複は許されない。instance_definition
/ singleton_definitionの各1行を削除してuntyped?側に統一するの
が妥当(実装がnilを返し得るかは要確認のうえ確定)。
- 検知すべきか: Yes — RBS仕様レベルのエラー、RigorがRBSパーサ経由 で読む段階で同様にエラーになる(なるべき)。
A-5. CLI#initializeの必須キーワード(3件)
Section titled “A-5. CLI#initializeの必須キーワード(3件)”def initialize(argv, out:, err:)def initialize: (?Array[String] argv, ?out: untyped, ?err: untyped) -> voidsigはargvもout:もerr:も全てoptionalだが、implは全て
required(out:には既定値が無い)。callerがsigを信じてCLI.new
を引数なしで呼ぶとArgumentErrorで落ちる。
- 検知すべきか: Yes — 契約上のlenienceがimplで守られていない。
- 解消方向: ADR 5(Robustness Principle)に従えばimpl側を寛容化、
すなわち
def initialize(argv = [], out: $stdout, err: $stderr)に 揃えるのが筋。これでself.startの挙動も維持される。
カテゴリB — Rigorでは検知しないのが適切(Steep固有の偽陽性)
Section titled “カテゴリB — Rigorでは検知しないのが適切(Steep固有の偽陽性)”今回は該当ゼロ。Steep 2.0が出した警告はすべて何らかの実体的な
不一致を反映していた。Rigorが独自の寛容性ポリシーで意図的に「検出
しない」と決めた領域が今回ヒットしなかったのは、sig/側がそもそも
かなり手書きで実装と乖離していて、寛容性の議論に到達するより先に基本
契約違反が露出したため。今後sigを修正したあと再走させると、本カテ
ゴリに分類すべきケースが現れる可能性は高い(例: untyped経由の
gradual受け入れをSteepが拒む等)。
カテゴリC — 適切な型付けで偽陽性として解消可能
Section titled “カテゴリC — 適切な型付けで偽陽性として解消可能”Steep側のフローセンシティブ性または核ライブラリのモデリングが粗いた めに発生している警告。Rigorのrobustness principleとcontrol-flow analysis (docs/type-specification/control-flow-analysis.md) が完成すれば自然に消えるはず、という意味で偽陽性扱い。
C-1. Array(union)強制変換(1件)
Section titled “C-1. Array(union)強制変換(1件)”lib/rigor/analysis/fact_store.rb:128:
def fact_targets(fact) Array(fact.target)endfact.targetはTarget | Array[Target]。RubyのArray()カーネル
メソッドは「Array[T]ならそのまま、Tなら[T]でラップ」する規約
なので、戻り値は当然Array[Target]。SteepはArray()の戻り値を
[T | Array[T]](要素1のタプル)と推論しており、ユニオン分岐越しの
specializationに踏み込めていない。
- Rigor視点:
Kernel#Arrayの組み込みカタログ( data/builtins/)に「ユニオン引数の場合は 各メンバを正規化してunify」という仕様を入れれば消せる。 - 当面のworkaroundは不要 — 偽陽性としてlenient設定下で warningに留まる。
C-2. Data.define派生クラスのinitializeオーバーライド(5件)
Section titled “C-2. Data.define派生クラスのinitializeオーバーライド(5件)”lib/rigor/analysis/fact_store.rb:26,32
のTarget / FactはData.define(:kind, :name)等で生成されたクラ
スにdef initialize(kind:, name:) ...で前処理を被せている。Steepは
Data.defineの自動生成シグネチャと手書きオーバーライドのキーワード
合致を解析しきれず、MethodParameterMismatchを5箇所で出している。
- Rigor視点:
Data.define(*members)の特化推論 (docs/type-specification/structural-interfaces-and-object-shapes.md) に加え、明示的に書かれたinitializeシグネチャを優先する規則を入れ れば消える。これはv0.0.4 / v0.1.0のロードマップに直接乗りそうな 特徴量。 - 暫定: sig側で
Target/FactのinitializeがData由来の 自動生成シグネチャと矛盾しないよう明示的に書ききることでSteep も黙る(現状のsig側はすでに手書きしてある)。Steepが拾えていないの はData由来の合成シグネチャ側で、RigorがData.define専用の 認識器を持てば優位に立てる領域。
アクション提案
Section titled “アクション提案”優先度順:
sig/rigor/type.rbsの述語メソッド戻り値型修正(A-1, 39件)- 機械的置換に近い。
def (top|bot|dynamic): () -> Trinaryに揃える。 - これだけで全54件中39件が消える。
- 機械的置換に近い。
environment.rbsの重複宣言整理(A-4, 3件)- 余分な行を削除し
untyped?側に統一。
- 余分な行を削除し
scope_indexerのsigに第4引数を追加(A-3, 1件)IntegerRange#lower/upperのsig修正(A-2, 2件)- 短期: sigを
Integer | Float | Symbolに。長期: ADR 3で番兵 表現を再検討。
- 短期: sigを
CLI#initializeの寛容化(A-5, 3件)def initialize(argv = [], out: $stdout, err: $stderr)に修正。
- カテゴリC系は当面lenient warningとして放置。Rigor側の
Kernel#Arrayモデリング /Data.define認識器が入った時点で再走 させ、消えたことを確認する。
# 一回限り (依存解決)nix develop --command make steep-install
# 走らせるnix develop --command make steep-check
# 個別ターゲット (オプション渡し)nix develop --command make steep ARGS="check --severity-level=error"Steepfileは現状D::Ruby.lenient構成。互換性チェック目的でwarning
を網羅的に拾うため(= strictより広く検出するため)この設定にしている。
将来make verifyチェーンに組み込むかどうかは、A-1〜A-5を解消した後
の残件を見て判断する。
v0.1.1リリース直前follow-up (2026-05-08)
Section titled “v0.1.1リリース直前follow-up (2026-05-08)”A-1〜A-5はすべてv0.1.xのTrack 4で着地済(docs/ROADMAP.md v0.1.1
Track 4 item 11 / 13 / 12参照)。再走(make steep-check)の結果を
v0.1.1リリース直前に再分類:
- 入力:
make steep-check(v0.1.1リリース候補ブランチ) - 集計: 8件 / 2ファイル(warning 8件のみ、error 0件)
| カテゴリ | 件数 | 内容 |
|---|---|---|
| A. 真のsig drift | 0 | A-1〜A-5は全て解消済 |
| B. Rigorでは検知しないのが適切 | 8 | Data.define do ... end override block / Kernel#Array narrowing / defのlambda default — SteepのRuby idiomサポートの限界に起因 |
| C. 偽陽性(Rigorの精密化で消える) | 0 | カテゴリBに再分類 |
v0.1.1で解消したerror(1件)
Section titled “v0.1.1で解消したerror(1件)”sig/rigor.rbs:67RBS::UnknownTypeName: Rigor::Cache::Store—Rigor::Cache::Storeはv0.1.1時点でもsig未整備(UNSIGNED_NAMESPACES入り)なので、参照をuntypedに変更して落とした。同時にattr_reader plugin_registry: untypedと?plugin_requirer: untypedをRunner宣言に追加(Track 2 slice 7で導入されたRunnerサーフェスをsigに反映)。Rigor::Cache::Storeの本格sigはv0.1.x維持タスクとしてdeferred(UNSIGNED_NAMESPACESから外す段階で書く)。
残る8件(warning)の分類
Section titled “残る8件(warning)の分類”すべてSteepのRuby idiom認識の限界に起因し、Rigorのコードベース側に 直すべき不一致は無い:
| 件数 | 場所 | 種類 | 性質 |
|---|---|---|---|
| 5 | lib/rigor/analysis/fact_store.rb:26-32 | Ruby::MethodParameterMismatch | Target = Data.define(...) do def initialize(kind:, name:); ...; end; endのoverride-block内def initializeをSteepが外側FactStoreのinitialize宣言と照合してしまう。Data subclassのsigと紐付けられない既知のSteep限界。runtimeはsuper(...)で正しくData#initializeを呼び出している。 |
| 1 | lib/rigor/analysis/fact_store.rb:128 | Ruby::MethodBodyTypeMismatch | Array(fact.target)で`fact.target: Target |
| 2 | lib/rigor/plugin/loader.rb:41 | Ruby::MethodParameterMismatch | def self.load(configuration:, services:, requirer: ->(name) { require name })のlambda defaultをSteepがKernel#requireのsig形と取り違える。Plugin名前空間にsigが無い(UNSIGNED_NAMESPACES)ことの副作用。 |
make steep-checkはerror 0 / warning 8で安定。make verify
チェーンには引き続き含めない(warning段階での意図的な乖離は許容)。
Plugin::* / Cache::Storeのsigが整備されたタイミングでwarningは
全て解消する見込み。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.