コンテンツにスキップ

upstream `ruby/rbs` PR — `Resolv::DNS`のタイプクラスによる戻り値型の絞り込み

upstreamのPRを準備する並行セッション(/Users/megurine/repo/ruby/rbs)への引き継ぎメモ。Mastodon解析サイクル(2026-05-28、789→2エラー)では、upstreamのPRが解消する一つのユーザー向けstdlib RBSギャップが残りました。CURRENT_WORKで参照されていたもう一つのギャップ(StringScanner#[])はupstreamにすでにマージ済みであることが判明しました。

1. StringScanner#[] — upstreamに取り込み済み、PRは不要

Section titled “1. StringScanner#[] — upstreamに取り込み済み、PRは不要”

upstream stdlib/strscan/0/string_scanner.rbs:501fcc16851時点のHEAD):

def []: (Integer | String | Symbol) -> String?

バンドル済みのrbs-3.10.0(Rigorが現在使用しているバージョン)にはまだ狭い(Integer) -> String?のシグネチャしかなく、これがMastodonトライアルでsignature_parser.rbscanner[:key]呼び出し箇所が誤検知する原因です。修正はupstream PRではなく、Rigor側でのrbs gemバージョン引き上げです。upstreamセッションのスコープ外です。

2. Resolv::DNS#getresources / #getresource / #each_resource — タイプクラスによる戻り値型の絞り込み

Section titled “2. Resolv::DNS#getresources / #getresource / #each_resource — タイプクラスによる戻り値型の絞り込み”

実際のPRの対象

Mastodon app/validators/email_mx_validator.rb:49:

records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }

.exchangeResolv::DNS::Resource::MX(およびIN::MXが継承)に定義されており、基底クラスのResolv::DNS::Resourceには定義されていません。現行のupstreamシグネチャ(stdlib/resolv/0/resolv.rbs:311):

def getresources: (dns_name name, singleton(Resolv::DNS::Query) typeclass) -> Array[Resolv::DNS::Resource]

要素型が上限のResolv::DNS::Resourceになっており、タイプクラスが決定する部分型が失われています。特定のサブクラスを渡す呼び出し側(dns.getresources(name, IN::MX) / IN::A / IN::AAAAという形式——実際にはほぼこれしか使わない)は型精度が落ちてしまいます。

いずれもstdlib/resolv/0/resolv.rbsResolv::DNSクラス内:

メソッドインターフェース
302getresource1件のResourceを返す
311getresourcesArray[Resource]を返す
221each_resourceブロックパラメータがResource
223extract_resourcesブロックパラメータがResource

fetch_resource(230行目)もタイプクラス引数を受け取りますが、Resourceを返す・ブロックに渡すわけではありません——ブロックには(Message, Name)が渡されます。スコープ外です。

提案する修正——有界ジェネリクスメソッド

Section titled “提案する修正——有界ジェネリクスメソッド”

RBSは有界型パラメータをサポートしています(先例: core/random.rbs:97def rand: ... | [T < Numeric] (::Range[T]) -> Tcore/encoding.rbs:136 / :182core/dir.rbs:919core/kernel.rbs:679 / :1407 / :1449 / :2279core/marshal.rbs:154core/ractor.rbs:557)。メソッドごとに1行のジェネリクスにするのが最もすっきりした形です。

def getresources: [T < Resolv::DNS::Resource] (dns_name name, singleton(T) typeclass) -> Array[T]
def getresource: [T < Resolv::DNS::Resource] (dns_name name, singleton(T) typeclass) -> T
def each_resource: [T < Resolv::DNS::Resource] (dns_name name, singleton(T) typeclass) { (T) -> void } -> void
def extract_resources: [T < Resolv::DNS::Resource] (Resolv::DNS::Message msg, dns_name name, singleton(T) typeclass) { (T) -> void } -> void

エッジケース — Resolv::DNS::Resource::ANY

Section titled “エッジケース — Resolv::DNS::Resource::ANY”

Resolv::DNS::Resource::ANY < Resolv::DNS::Querystdlib/resolv/0/resolv.rbs:833)はResolv::DNS::Resourceのサブクラスではありません——Query配下でResourceと並列の位置にあります。ANYをタイプクラスとして渡すと、マッチするリソース型の異種混合が返され、静的には裸のResourceエンベロープになります。有界パラメータ[T < Resolv::DNS::Resource]ANYを除外するため、旧来の広いオーバーロードがフォールバックとして並存します。

def getresources: [T < Resolv::DNS::Resource] (dns_name name, singleton(T) typeclass) -> Array[T]
| (dns_name name, singleton(Resolv::DNS::Resource::ANY) typeclass) -> Array[Resolv::DNS::Resource]

(3つの兄弟メソッドも同じ形状。)

代替案 — サブクラスごとのオーバーロード

Section titled “代替案 — サブクラスごとのオーバーロード”

有界ジェネリクスがupstreamのテストコーパスで扱いにくい推論出力を生む場合、明示的なオーバーロード方式は冗長さと引き換えに予測可能性をもたらします。完全なセットはResolv::DNS::Resource::IN::{A, AAAA, CNAME, HINFO, LOC, MINFO, MX, NS, PTR, SOA, SRV, TXT, WKS}Resolv::DNS::Resource::Genericサブクラス(およびANYフォールバック)をカバーします。約16オーバーロード行×4メソッド≈64行——許容範囲内ですが、ジェネリクス方式が機能するなら後者が望ましいです。

  • RigorはこのギャップのためのRigor独自のsig/オーバーレイやstdlibオーバーレイプラグインを提供する予定はありません。判断(このノートの作成セッションのコミットメッセージを参照)はOptionD + A: upstreamに委ねてドキュメント化する——それまでの間、影響を受けるプロジェクト向けにユーザー側でのsignature_paths: / # rigor:disable回避策を案内する。
  • Mastodonの残存エラーはdocs/CURRENT_WORK.mdの「2件の残存エラー」の項にResolv側ギャップとして記録されています。
  • upstreamのPRがマージされ新しいrbs gemがリリースされれば、Rigorのバンドルrbsバージョン引き上げによってMastodonの該当箇所が自動的に解消されます(StringScanner#[]の箇所も同時に)。
  • upstream rbs HEAD: コミットfcc16851(2026-05-28)にて調査。
  • 比較対象のバンドル済みrbs-3.10.0
  • Mastodon該当箇所: app/validators/email_mx_validator.rb:49
  • CURRENT_WORK「Stdlib RBSカバレッジギャップパターン」項目。

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