クラス
この章はクラス側の型付けを扱います — 異なる位置でのselfの意味、定数の解決、そしてRigorがattr_*、Data.define、Struct.new宣言をどう読むか。物語というよりリファレンスです: 一度通して読み、必要なセクションに戻ってきてください。
この章の内容 インスタンス側とクラス側の
self· 定数 ·attr_*· インスタンス変数 ·Data.define·Struct.new· 継承 ·class/singleton(C)型 · カスタム===· エイリアスクラス · モジュール ·protected/private
インスタンス側とクラス側のself
Section titled “インスタンス側とクラス側のself”インスタンスメソッド本体の内側では、selfは囲むクラスのNominal[T]です:
class User def name self # Nominal[User] endendシングルトンメソッド本体の内側(def self.fooまたはdef User.foo)では、selfはSingleton[T] — インスタンスではなくクラスオブジェクト自体 — です:
class User def self.find(id) self # Singleton[User] endend
User # Singleton[User]User.find(1) # Nominal[User] (RBSで宣言)User.new # Nominal[User]この区別はメソッドディスパッチで重要です: インスタンスメソッドはNominal[User]で実行され、シングルトンメソッドはSingleton[User]で実行されます。Rigorはメソッドがどちら側にあるかを知るために、RBSシグのコロンの右側(def self.find: (Integer) -> User)を読みます。
定数ルックアップは4つのソースを順に辿ります:
- 字句スコープ。
Fooがclass A; module B; ...の内側で参照されている場合、RigorはA::B::Foo、A::Foo、Fooを探します。 - RBSコアとバンドルされたstdlib。
String、Integer、Symbol、Array、Pathname、URI、OptParse、JSON、YAMLなど。 - プロジェクトRBS。 プロジェクトの
sig/ファイルがルックアップに追加されます。 - インソースクラス探索。RBSが存在しない場合、Rigorは
class Foo、module Bar、定数代入(MAX = 100)を辿ります。
MAX = 100class Counter def initial = MAXend
Counter.new.initial # Constant<100> — 定数値が # インソースクラスルックアップを # 通じて伝播するRigorがたたみ込める右辺を持つ定数にはConstant<value>型が付きます。そうでない定数には、より広いRBS消去形式が付きます。
attr_reader、attr_writer、attr_accessor
Section titled “attr_reader、attr_writer、attr_accessor”Rigorはattr_*宣言を読み、メソッド定義として扱います。リーダーの戻り値型は対応するインスタンス変数の推論された型と一致します:
class User attr_reader :name
def initialize(name) @name = name endend
u = User.new("Alice")u.name # Constant<"Alice"> — インソースディスパッチ + # インスタンス変数追跡を通じてattr_writerはセッターを公開します; attr_accessorは両方を公開します。セッターの引数型は呼び出し元が提供するものです。def.ivar-write-mismatchルール(v0.1.2)は、同じクラスボディ内の同じインスタンス変数への2つの書き込みが具体クラスで一致しているかチェックします — 正確な契約(contract)については第8章 — エラーの読み方を参照してください。明示的なインスタンス変数型を作成せずに、同じクラス内でのStringからArrayへの誤ったリバインドをキャッチできます。
メソッドをまたいだインスタンス変数
Section titled “メソッドをまたいだインスタンス変数”Rigorはクラスのすべてのメソッドにわたってインスタンス変数の事実を蓄積します:
class Counter def initialize @count = 0 # init後の@count: Constant<0> end
def bump @count += 1 # @countがint<1, max>に再バインドされる end
def value @count # int<0, max> (見られた書き込みのユニオン) endend各読み取り地点でのインスタンス変数型は、静的に見えるすべての書き込みのユニオン(union、合併型とも)です — 同じクラスの別のメソッドからの書き込みも含みます。
Data.define
Section titled “Data.define”Data.defineは小さなイミュータブルな構造体を生成します。Rigorは宣言を認識し、コンストラクタのアリティ(arity)、フィールドごとのアクセサ、結果のクラス型を公開します:
Point = Data.define(:x, :y)
p = Point.new(x: 3, y: 4)assert_type("Nominal[Point]", p)assert_type("Constant<3>", p.x)assert_type("Constant<4>", p.y)探索はdefine_methodスタイルのブロック本体も辿るので、Point = Data.define(:x, :y) do ... endでも動作します。合成されたキーワード引数コンストラクタをオーバーライドするブロック定義のdef initialize(...)も含みます(v0.1.2)。同じルールがConst = Struct.new(*Symbol) do ... endにも適用されます — ブロックボディのメソッド発見が両方の形式にわたって均一に組み合わせられます。
Struct.new
Section titled “Struct.new”Struct.new(*Symbol)は位置引数コンストラクタに加えてData.defineと同じアクセサを生成します。Rigorは両方の形式を処理します:
Coord = Struct.new(:x, :y)
c = Coord.new(10, 20)assert_type("Constant<10>", c.x)assert_type("Constant<20>", c.y)Structはミュータビリティを追加します(アクセサはライターでもある)ので、インスタンス変数スタイルの蓄積が適用されます。Dataは読み取り専用です。
継承とメソッド解決
Section titled “継承とメソッド解決”Nominal[Subclass]でメソッドを呼び出すと、Rigorはクラス階層を辿ります: まずサブクラスのRBS / インソース本体、次に各祖先のRBS / 本体、次に宣言順に含まれるモジュール。メソッドを定義した最初のものが勝ちます。
階層は次から読まれます:
- RBSの
class Foo < Bar宣言。 - インソースの
class Foo < Bar行。 - Rigorが辿った
include/prepend/extend呼び出し。
階層が静的に不完全な場合(クラスがRigorが見つけられない親を参照している)、レシーバー型は最も深い既知の祖先にフォールバックします — Rigorが宣言を見たクラスに対しては、Dynamic[Top]になることはありません。
class型とsingleton(C)型
Section titled “class型とsingleton(C)型”メソッドシグネチャが「クラスオブジェクト自体」を返すことがあります:
class Foo def self.factory: () -> Foo # インスタンスを返す def self.subclasses: () -> Array[singleton(Foo)] # クラスオブジェクトを返すendsingleton(Foo)はクラスオブジェクトFooの型です。Singleton[Foo](Rigorの内部キャリア(carrier)表示形式)も同じ概念です。(Array[Foo]での)Fooは「Fooのインスタンス」/ Nominal[Foo]を意味します。
singleton(Foo)でインスタンスメソッドを呼び出すのはエラーです。ただしFoo自体がそのシングルトンメソッドを定義している場合は除きます — Stringはsingleton(String)で、String#upcaseはインスタンスにあるので、String.upcaseはcall.undefined-methodをフラグします。
カスタムcase_eq(===)
Section titled “カスタムcase_eq(===)”RigorはClass / Module / Range / Regexpに対して===を認識します — これらは標準のcase x; when …の形式です。ユーザークラスへのカスタムcase_eq実装は認識されません:
class IPv4 def self.===(s) s.match?(/\A\d+\.\d+\.\d+\.\d+\z/) endend
case some_inputwhen IPv4 # Rigorはここで`some_input`をナローイングしません — # IPv4.===はユーザー定義のcase等値で、エンジンは # 特定のクラスにナローイングするとは証明できません。 some_inputendこのような場合、明示的なis_a? / respond_to?ガードを書くか、===メソッドにRBS::Extendedのpredicate-if-trueディレクティブを使ってください(第7章参照)。
定数宣言エイリアスクラス
Section titled “定数宣言エイリアスクラス”一部のRubyイディオムは定数代入でクラスエイリアスを作ります:
YAML = Psych右辺がクラス自体の場合、Rigorはレシーバー型付けのためにエイリアスを追います — YAML.load(...)はPsych.load(...)として扱われます。しかしメソッド存在チェックはエイリアス名に対して意図的に沈黙します;解析器はより多くのコンテキストなしに意図的なエイリアスと偶発的なシャドウを区別できないので、YAML.unknownはcall.undefined-methodを発火しません。診断が必要な場合は正規名を使ってください。
型付けの目的では、module M; def foo; end; endはクラスと構造的に似ています。メソッドは同じように参照されます; include MはMのメソッドをインクルードするクラスの階層に追加します。
extend selfスタイルのミックスインパターン(module_function / extend self)が認識されます — インスタンス側とシングルトン側の両方が同じメソッドを公開します。
protectedとprivate
Section titled “protectedとprivate”Rigorは可視性修飾子を読み、def.method-visibility-mismatchルール(将来)の限定的なコンテキストでそれらを考慮します。今日、外部レシーバーへのプライベートメソッド呼び出しは診断を発火しません — 可視性は型システムの問題というよりもrubocop-styleリンターの関心事です。
次に読むもの
Section titled “次に読むもの”第7章はRBSとRBS::Extendedを扱います — 推論だけでは証明できないものを超えるための外部シグネチャ表面です。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.