🐼

RBSのPrivateの処理を追ってみる

2024/08/31に公開

RubyのPrivate

こいつのことです。初学者なので勘違いしていましたがキーワードではなくメソッドらしい……。

class MyClass
  def hoge
    puts 'test'
  end

  private <--こいつ
  def hogehoge
  end
end

公式リファレンス

private() -> nil[permalink][rdoc][edit]
private(name) -> String | Symbol
private(*name) -> Array
private(names) -> Array
メソッドを private に設定します。

引数なしのときは今後このクラスまたはモジュール定義内で新規に定義されるメソッドを関数形式でだけ呼び出せるように(private)設定します。

引数が与えられた時には引数によって指定されたメソッドを private に設定します。

可視性については クラス/メソッドの定義/呼び出し制限 を参照して下さい。

https://docs.ruby-lang.org/ja/latest/class/Module.html#I_PRIVATE

rbs

rbsでも同様関数として定義されています。

  def private: () -> nil
             | (Symbol method_name) -> Symbol
             | (Symbol, Symbol, *Symbol method_name) -> Array[Symbol]
             | (string method_name) -> string
             | (interned, interned, *interned method_name) -> Array[interned]
             | (Array[interned]) -> Array[interned]

RBS内部で関数をprivateにしている箇所

# rbs/prototype/rb.rb
def process(node, decls:, comments:, context:)
case node.type
...
when :public, :private
  accessibility = __send__(node.children[0])
  if args.empty?
    decls << accessibility
  else
    args.each do |arg|
      if arg && (name = literal_to_symbol(arg))
        if (i, _ = find_def_index_by_name(decls, name))
          current = current_accessibility(decls, i)
          if current != accessibility
            decls.insert(i + 1, current)
            decls.insert(i, accessibility)
          end
        end
      end
    end
    # For `private def foo` syntax
    current = current_accessibility(decls)
    decls << accessibility
    process_children(node, decls: decls, comments: comments, context: context)
    decls << current
  end
...

どうやら引数がない場合declsにそのままアクセシビリティが登録され、引数ある場合は関数を名前で探して登録、defの前のprivateがつてるものは別途処理をしているようです。
単純にNodeのタイプがprivate関数であればdeclsに現在のアクセシビリティが登録される処理を行っていそうです。

current_accessibility

# rbs/prototype/rb.rb
def current_accessibility(decls, index = decls.size)
  slice = decls.slice(0, index) or raise
  idx = slice.rindex { |decl| decl == private || decl == public }
  if idx
    _ = decls[idx]
  else
    public
  end
end

現在のアクセシビリティが何なのかをrindexを使い、privateまたはpublicが最後のどこでできたのかを探索して調べる関数のようです。
processの中でこれを使いアクセシビリティを更新すべきかどうが判断しているみたいですね。

private

# rbs/prototype/rb.rb

 def private
  @private ||= AST::Members::Private.new(location: nil)
 end

@privateがnilの場合に新しいAST::Member::Private@privateに代入する関数のようです。
@privateを保持している理由は使っている場所が見当たらず背景がよくわかりませんでした。(初期化に負荷がかかるから?)

おわり

privateがキーワードではなくメソッドだったのでASTの解析がどうなってるんだろうなと思い実装を追ってみました!
間違っている箇所があればコメントしてくだい!!

GitHubで編集を提案
SMARTCAMP Engineer Blog

Discussion