
はじめに
こんにちは、株式会社 Techouse でエンジニアをしている@kuragane1122です。
私が所属するチームでは、「Ruby に型は必要か?」という議論が定期的に起きます。 返り値が何かわからないままコードを書いてバグになった経験や、型があれば防げたのではという場面を目にするたびに話題になります。一方で、RBS の記述コストやメンテナンス負荷がペイするのかという懸念も根強く、答えが出ないまま議論が続いています。
そんな状況で参加した RubyKaigi 2026(2026年4月22日〜24日、函館)で、その問いの答えとなりそうなセッションに出会いました。@dak2 さん(GitHub: @dak2 / X: @_dak2_)による「No Types Needed, Just Callable Method Check」です。
本記事では、セッションの内容を解説しつつ、紹介された gem Method-Ray を実際に動かした結果をレポートします。
RubyKaigi 2026 で出会った問い
発表の出発点は「型チェックの恩恵の多くは NoMethodError の防止に集約されるのではないか」という問いです。
@dak2 さんは Timee で RBS::Inline + Steep を日常的に使ってきた経験から、型システムには明確な価値があると認めつつも、次のような課題を感じるようになったと話していました。
- AI が生成するコードの品質が向上し、型注釈なしでもデプロイ可能なコードが増えている
- 型ファイルの記述・CI 待機・メンテナンスといった運用コストが積み重なる
- 動的メタプログラミングや gem 境界では完全な型付けが難しい
AI 時代と型の関係を示す研究
この主張を裏付けるデータとして、発表中に ArXiv の論文(arXiv:2412.20545)が引用されていました。その中の興味深い結果が次の点です。
- Ruby・Python・JavaScript は LLM によるコード生成が最速・最安・最も安定している
- 型安全性は改善するが、コード品質は低下するというケースが存在する

こうした研究にも触れながら、@dak2 さんは「型は人間のために設計されたガードレールであって、LLM 向けに設計されたものではない」と述べています。
「でも、型がゼロでいいわけではない」
ただ、「じゃあ型はゼロでいいのか」というと、そうでもないというのが @dak2 さんの立場です。スライドには "But, some types are needed" とあり、AI の非決定的な振る舞いを制御するためのガードレールは依然として必要だと述べています。
そこからさらに踏み込んで、「型を書いて得られる一番大きな恩恵は NoMethodError の防止だ」という整理を提示します。だとすれば、型注釈を書かずにその一点だけを静的に防げれば十分ではないか——そこから Method-Ray の着想につながっていきます。
開発ループのボトルネックが変わった
先ほどの研究が示すように、AI は Ruby コードを速く・安く・安定して生成できます。コードを書くこと自体のスピードはもはや人間のボトルネックではなく、「生成されたコードが正しいかどうかを確認する速さ」 に移ってきている——というのが私自身の実感でもあります。
実際、今回の RubyKaigi でも複数のスピーカーが「AI が登場してから特にこの一年、自分でコードを直接書く量が明らかに減った」という話をしており、体感では半数近くの発表でそういった言及があったように思います。コードを書く作業がAIに移譲されていく中で、人間の仕事は「生成されたコードを検証すること」にシフトしつつある——そういう時代感がこのセッション、もといRubyKaigi2026の背景にあります。
だからこそ Method-Ray のコアを Rust で実装し、CLI のパフォーマンスにこだわっています。型注釈を書く手間もなく、数秒で結果が返ってくる静的チェックというのは、このボトルネックに対する一つの答えと言えます。
Method-Ray とは
@dak2 さんが発表の中で紹介したのが、自身が開発中の OSS ツール Method-Ray です。
コンセプト:「Do one thing and do well」
Method-Ray が提供するのは、シンプルに一点です。
「このメソッドは呼び出せるか?」を Yes / No で答える
以下は意図的にやらないこととして設計されています。
| 機能 | Method-Ray |
|---|---|
| 型エラーレポート | ✗ |
| nil-safety 警告 | ✗ |
| ジェネリックの分散チェック | ✗ |
| LSP / エディタ統合 | ✗ |
| 型推論結果の表示 | ✗ |
Unix 哲学の「一つのことをうまくやれ(Do one thing and do well)」を体現した、意図的に狭い(intentionally narrow)設計です。
アーキテクチャ
Method-Ray はインターフェースを Ruby、コア処理を Rust で実装しています。処理は大きく 4 ステップで構成されます。

各ステップの詳細です。
① パース:ruby-prism で Ruby コードを AST に変換します。メモリ効率化のため bumpalo のアリーナアロケータを採用しており、ノードごとのヒープ割り当てを避けて処理終了時に一括解放します。
② グラフ構築:AST を走査しながら MethodRegistry(HashMap)にメソッド情報を登録します。("String", "length") → MethodInfo { return: Integer } のような形で O(1) 検索できる状態を作ります。
③ 型フロー追跡:型グラフ上で型情報を頂点と辺として表現し、MethodCallBox という「リアクティブ制約」を使ってメソッド呼び出し可能性を判定します。変数が再代入されたとき(例:x = "hello" → x = 42)にも自動で型を更新して再チェックします。
④ 診断出力:ソース位置情報を全段階で保持しているため、エラー箇所を行・列単位で出力します。
Rust を選んだ理由として、発表では JavaScript の Linter である Oxlint が ESLint より 50〜100 倍高速という例が挙げられていました。「CI が結果を返す速さ」がボトルネックになった時代に、CLI のパフォーマンスを妥協しないという設計判断です。
既存ツールとの位置付け
| ツール | RBS 必須 | アプローチ |
|---|---|---|
| Steep | 必須 | RBS 署名検証 |
| Sorbet | 必須 | 型フロー検査 |
| TypeProf | 不要 | 抽象解釈で型を推論・提案 |
| Method-Ray | 不要 | メソッド呼び出し可能性の Yes/No 判定のみ |
TypeProf との違いは「推論結果を表示・提案するかどうか」です。Method-Ray は一切表示せず、「呼べるか呼べないか」だけを返します。
実際に動かしてみた
百聞は一見にしかず、実際に手元で動かしてみました。
環境構築
Ruby 3.4 以上が必要です。インストールはこれだけです。
gem install method-ray
依存関係は rbs ~> 3.0 のみで、Rails 環境は不要です。今回は次のような最小構成で検証しました。
# Gemfile source "https://rubygems.org" ruby "3.4.0" gem "method-ray"
bundle exec methodray check path/to/file.rb
ケース1:ローカル変数の型を追跡する
リテラルから型が確定しているローカル変数に対して、存在しないメソッドを呼んでいる場合を検出します。
# price_calculator.rb class PriceCalculator def apply_discount base_price = 1000 discounted = base_price.upcase # Integer に upcase は存在しない discounted end def build_label prefix = "Price: " prefix.abs # String に abs は存在しない end end
$ bundle exec methodray check price_calculator.rb
price_calculator.rb:4:28: error: undefined method `upcase` for Integer
discounted = base_price.upcase
^
price_calculator.rb:10:11: error: undefined method `abs` for String
prefix.abs
^
変数に型アノテーションは一切書いていませんが、リテラル値から型を推論して両方検出しています。
ケース2:同クラスのメソッド戻り値を追跡する
同じクラス内のメソッドが返す型も追跡します。
# report.rb class Report def record_count 42 end def formatted_count record_count.upcase # Integer に upcase は存在しない end def title "Monthly Report" end def title_as_number title.length.strip # length は Integer → Integer に strip は存在しない end end
$ bundle exec methodray check report.rb
report.rb:7:17: error: undefined method `upcase` for Integer
record_count.upcase
^
report.rb:15:17: error: undefined method `strip` for Integer
title.length.strip
^
title.length.strip のようなチェーンも、title が String を返し length が Integer を返す流れを正しく追跡して検出できています。
ケース3:継承チェーンとモジュールの解釈
v0.1.10 で継承チェーンの解決が、v0.1.9 でモジュール include の解釈が実装されました。
# animal.rb module Greetable def greet "Hello, I am #{name}" end end class Animal include Greetable def name "Animal" end end class Dog < Animal def bark greet.upcase # greet は String を返す → upcase は有効 end def invalid_call name.push("!") # name は String → push は Array のメソッド end end
$ bundle exec methodray check animal.rb
animal.rb:21:9: error: undefined method `push` for String
name.push("!")
^
include したモジュールのメソッドや継承ツリーを追跡した上で判定しています。有効な呼び出し(greet.upcase)は通過し、誤った呼び出し(name.push)のみ検出しています。
ケース4:メソッドチェーンの途中の型変化
メソッドチェーンの途中で型が変わるケースも追跡します。
# text_processor.rb class TextProcessor def extract_words text = "hello world" text.split(" ").strip # split → Array → Array に strip は存在しない end def compute base = 100 result = base.to_s.abs # to_s → String → String に abs は存在しない result end end
$ bundle exec methodray check text_processor.rb
text_processor.rb:4:20: error: undefined method `strip` for self
text.split(" ").strip
^
text_processor.rb:9:23: error: undefined method `abs` for String
result = base.to_s.abs
^
1行目のエラーメッセージが for self という表示になっていて少し読みにくいですが、split の戻り値に strip が存在しないことは検出できています。base.to_s.abs では to_s 後が String であることを正しく追跡しています。
壁に当たった — 現時点での制限
ただ、実際に触ってみると思ったよりすぐ壁に当たりました。
メソッドの引数は型推論できない
最初につまずいたのが引数まわりです。メソッドの引数には型情報がないため、引数を起点にしたコードは追跡できません。
# 引数ベースのコードは検出されない class PriceFormatter def format(price_str) # price_str の型は不明 amount = price_str.to_i # to_i の戻り値の型も不明のまま amount.upcase # Integer に upcase は存在しないが、スルーされる end end
$ bundle exec methodray check price_formatter.rb # 何も出力されない
RBS のような型注釈がない引数は「型不明」として扱われるため、そこから派生した変数の型も解決できません。ケース1〜4 の例がリテラルやクラス内メソッドの戻り値を使っているのはそのためです。
複数ファイルをまたいだ参照は未対応
現在の Method-Ray は単一ファイルの解析のみに対応しています。別ファイルで定義されたクラスのメソッドは検出できません。
# order.rb(別ファイル) class Order def total_price items.sum(&:price) end end
# checkout.rb class Checkout def process(order) order.total_price.upcase # Integer に upcase は存在しないが... end end
$ bundle exec methodray check checkout.rb # エラーは何も出ない(order の型が解決できないため)
order.total_price が何を返すか、単一ファイルの範囲では判定できないため、このような呼び出しは素通りします。
外部 gem のメソッドは RBS 対応次第
rbs ~> 3.0 に依存しているため、RBS ファイルが存在する gem であれば解釈できます。一方、RBS 未整備の gem のメソッドは解決できず、誤検知(false negative)が発生します。
@dak2 さんも発表の締めで「Production-ready ではない」と明言しており、次の課題が残っています。
- 複数ファイル解析
- gem の RBS 対応
- 事前定義 RBS ファイルの処理
AI 時代に最大バリューを発揮する組み合わせ
現時点の制限を踏まえた上でも、特定の使い方をすれば今すぐ価値を発揮できると感じました。
それが「AI が生成したコードの CI チェック」としての活用です。
AI コード生成 × Method-Ray のループ

AI が生成したコードが NoMethodError を含んでいれば、人間がレビューする前に自動で検出・差し戻せます。CI への組み込みはシンプルです。
# .github/workflows/method_ray.yml name: Method-Ray Check on: [push, pull_request] jobs: method-ray: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: bundle exec methodray check app/services/
なぜこの組み合わせが刺さるのか
Steep や Sorbet の導入は「型システム全体を整備する投資」です。RBS ファイルの記述・メンテナンス・チームへの浸透と、相応のコストが伴います。
Method-Ray は「NoMethodError だけを潰す軽量な投資」です。設定ほぼゼロ・型注釈なし・RBS 不要で、この一点だけを静的に検出します。
AI がコードを大量生成するほど、型システム全体の整備よりピンポイントに速いフィードバックの価値が相対的に上がる——これが @dak2 さんの問いの核心だったと、実際に触ってみて改めて感じました。
まとめ
Method-Ray は「型注釈なしで NoMethodError だけを静的に検出する」ツールです。現時点では単一ファイル解析のみで Production-ready とは言えませんが、AI コード生成の検証に組み込むというユースケースでは今すぐ価値を発揮できます。
型システムを導入するコストを払えていないチームにとって、Method-Ray は最初の一歩として試しやすい選択肢ではないでしょうか。
@dak2 さんはセッションの締めで「決定的な答えは自分にはない、各チームで考えるべき」と述べていました。型が必要かどうかではなく、どのレベルの静的チェックがチームのコンテキストに合うか——この問いをぜひ自分のチームでも考えてみてください。
Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。