Upgrade to Pro — share decks privately, control downloads, hide ads and more …

gem_rbs_collection へのコントリビュートから始める Ruby の型の世界/c...

gem_rbs_collection へのコントリビュートから始める Ruby の型の世界/contributing-gem-rbs-collection

Masatoshi Moritsuka

October 05, 2024
Tweet

More Decks by Masatoshi Moritsuka

Other Decks in Programming

Transcript

  1. 自己紹介 • 名前: 森塚 真年 • GitHub: @sanfrecce-osaka • Twitter(X):

    @sanfrecce_osaka • 所属: 株式会社エンペイ • 開催コミュニティ: Machida.rb・Hirakata.rb • 最も好きな機能: パターンマッチ
  2. 1. コントリビュート先を確認する • 組み込みライブラリである • => rbs(https://github.com/ruby/rbs) • rbs/stdlib(https://github.com/ruby/rbs/tree/master/stdlib) で型が提供されている

    • => rbs(https://github.com/ruby/rbs) • gem のリポジトリに sig ディレクトリがある • => gem 自体のリポジトリ • それ以外 • => gem_rbs_collection(https://github.com/ruby/gem_rbs_collection)
  3. 2. 開発環境の構築 $ git clone https://github.com/ruby/rbs.git 略... $ bin/setup 略...

    $ bin/init_new_gem roo Gem version you want to add (MAJOR.MINOR is recommended. e.g. 4.2): > 2.10 型を追加する gem のバージョンを入力
  4. 2. 開発環境の構築 $ git clone https://github.com/ruby/rbs.git 略... $ bin/setup 略...

    $ bin/init_new_gem roo Gem version you want to add (MAJOR.MINOR is recommended. e.g. 4.2): > 2.10 Your GitHub account if you want to become the maintainer of RBS for this gem (default: skip adding you to the maintainer) We recommentd to add your account to maintain the RBS actively. See https://github.com/ruby/gem_rbs_collection/blob/main/docs/CONTRIBUTING.md#gem-reviewer > sanfrecce-osaka ・gem の型の maintener になる場合は GitHub のアカウント名を入力 ・ならない場合は入力無しで OK
  5. 2. 開発環境の構築 $ git clone https://github.com/ruby/rbs.git 略... $ bin/setup 略...

    $ bin/init_new_gem roo Gem version you want to add (MAJOR.MINOR is recommended. e.g. 4.2): > 2.10 Your GitHub account if you want to become the maintainer of RBS for this gem (default: skip adding you to the maintainer) We recommentd to add your account to maintain the RBS actively. See https://github.com/ruby/gem_rbs_collection/blob/main/docs/CONTRIBUTING.md#gem-reviewer > sanfrecce-osaka create gems/roo/2.10/roo.rbs create gems/roo/2.10/_test/test.rb create gems/roo/2.10/_test/metadata.yaml create gems/roo/2.10/manifest.yaml create gems/roo/_reviewers.yaml The boilerplate for roo gem has been generated. Start writing the RBS! We recommend to focus on the main API of roo. See the CONTRIBUTING.md for more information. boilerplate が生成される
  6. 参考: generator での型の生成 • 以前は generator を利用する方法が推奨されていた • https://speakerdeck.com/fugakkbn/types-teaches-success-what- will-we-do?slide=74

    • gem_rbs_collectionでジェネレーターが推奨されなくなった • https://fuga-ch85.hatenablog.com/entry/generator-not-recommend
  7. 3.1. generator を利用して型を生成する https://github.com/ruby/gem_rbs_collection/pull/665/commits/5a8699243974da630ce1c90c4c7b9cbbee727a32 $ git submodule add \ https://github.com/roo-rb/roo.git

    gems/roo/2.10/_src $ cd gems/roo/2.10/_src/lib $ rbs prototype rb -o ../../ roo Processing `roo`... Generating RBS for `roo/base.rb`... - Writing RBS to `../../roo/base.rbs`... Generating RBS for `roo/constants.rb`... - Writing RBS to `../../roo/constants.rbs`... Generating RBS for `roo/csv.rb`... - Writing RBS to `../../roo/csv.rbs`... Generating RBS for `roo/errors.rb`... ...
  8. 3.1. generator を利用して型を生成する https://github.com/ruby/gem_rbs_collection/pull/665/commits/5a8699243974da630ce1c90c4c7b9cbbee727a32 module Roo class Excelx class Extractor

    @path: untyped @options: untyped include Roo::Helpers::WeakInstanceCache COMMON_STRINGS: { t: "t", r: "r", s: "s", ref: "ref", html_tag_open: "<html>", html_tag_closed: "</html>" } def initialize: (untyped path, ?::Hash[untyped, untyped] options) -> void private def doc: () -> untyped def doc_exists?: () -> untyped end end end 生成される型の例
  9. 3.2.1 標準添付ライブラリに型がない場合 • 依存している matrix gem に型がなかった • time や

    csv は rbs/stdlib に型がある • 一方で gem 自体に型を同梱する Pull Request が出ている • https://github.com/ruby/matrix/pull/16
  10. 3.2.2 クラス自体に型を付けたい場合 https://github.com/ruby/gem_rbs_collection/blob/257e560cb101c5369b56aef9f3fe2a0c8f3f7e8e/gems/roo/2.10/ roo/libre_office.rbs#L2 Roo::LibreOffice: Roo::OpenOffice Roo::LibreOffice: singleton(Roo::OpenOffice) # cf.

    https://moneyforward-dev.jp/entry/2023/10/13/rbs-new-syntaxes class Roo::LibreOffice = singleton(Roo::OpenOffice) これだと Roo::OpenOffice のインスタンスになってしまう 正解はこう rbs 3.0.0 からはこう書いた方が良い
  11. 3.2.3 メタプロで定義されたメソッドの場合 https://github.com/roo-rb/roo/blob/v2.10.1/lib/roo/base.rb#L116-L122 %i(first_row last_row first_column last_column).each do |key| ivar

    = "@#{key}".to_sym define_method(key) do |sheet = default_sheet| read_cells(sheet) instance_variable_get(ivar)[sheet] ||= first_last_row_col_for_sheet(sheet)[key] end end メタプログラミングで定義されているメソッドは rbs prototype で型が生成されない
  12. 3.2.3 メタプロで定義されたメソッドの場合 https://github.com/ruby/gem_rbs_collection/blob/257e560cb101c5369b56aef9f3fe2a0c8f3f7e8e/gems/roo/2.10/ roo/base.rbs#L78-L84 def first_row: (?String | Integer sheet)

    -> (Integer | nil) def last_row: (?String | Integer sheet) -> (Integer | nil) def first_column: (?String | Integer sheet) -> (Integer | nil) def last_column: (?String | Integer sheet) -> (Integer | nil) 愚直に定義する
  13. 3.2.3 メタプロで定義されたメソッドの場合 https://github.com/roo-rb/roo/blob/v2.10.1/lib/roo/base.rb#L226-L242 # when a method like spreadsheet.a42 is

    called # convert it to a call of spreadsheet.cell('a',42) def method_missing(m, *args) # #aa42 => #cell('aa',42) # #aa42('Sheet1') => #cell('aa',42,'Sheet1') if m =~ /^([a-z]+)(\d+)$/ col = ::Roo::Utils.letter_to_number(Regexp.last_match[1]) row = Regexp.last_match[2].to_i if args.empty? cell(row, col) else cell(row, col, args.first) end else super end end #a1 や #aa42 といったメソッドが無限に生える
  14. 3.3. 今回のやり方で型を付けてみて • Pros • コードを読むときの助けになる • 正確な型を付けやすい • Cons

    • 無限に時間がかかる • リリース頻度によってはメンテが非常に辛くなる
  15. 参考: prototype 以外の型の生成方法 • orthoses • https://github.com/ksss/orthoses • typeprof •

    https://github.com/ruby/typeprof • rbs-dynamic • https://github.com/osyo-manga/gem-rbs-dynamic • rbs_goose • https://github.com/kokuyouwind/rbs_goose
  16. 参考: prototype 以外の型の生成方法 • rbs-trace • https://github.com/sinsoku/rbs-trace • rbs_activesupport •

    https://github.com/tk0miya/rbs_activesupport • rbs_activemodel • https://github.com/tk0miya/rbs_activemodel • rbs_active_hash • https://github.com/tk0miya/rbs_active_hash
  17. 4.1. _test/test.rb にテスト対象を使ったコードを書く require 'roo' file_name = './test.xlsx' xlsx =

    Roo::Excelx.new(file_name) xlsx.info xlsx.sheets xlsx.sheet('Info').row(1) xlsx.sheet(0).row(1) ・テスト対象のメソッドを呼び出すだけで OK ・test.xlsx を実際に用意する必要はない
  18. 4.2. bin/test を実行 % bin/test gems/roo/2.10 Testing gems/roo/2.10... Fetching gem

    metadata from https://rubygems.org/......... Resolving dependencies... Writing lockfile to /xxx/gem_rbs_collection/gems/roo/2.10/_test/Gemfile.lock Using csv:0 (/xxx/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/rbs-3.5.3/stdlib/csv/0) Using date:0 (/xxx/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/rbs-3.5.3/stdlib/date/0) Using forwardable:0 (/xxx/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/rbs-3.5.3/stdlib/forwardable/0) Using nokogiri:1.11 (/xxx/gem_rbs_collection/gems/nokogiri/1.11) Using openssl:0 (/xxx/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/rbs-3.5.3/stdlib/openssl/0) Using roo:2.10 (/xxx/gem_rbs_collection/gems/roo/2.10) Using rubyzip:2.3 (/xxx/gem_rbs_collection/gems/rubyzip/2.3) Using socket:0 (/xxx/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/rbs-3.5.3/stdlib/socket/0) Using uri:0 (/xxx/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/rbs-3.5.3/stdlib/uri/0) It's done! 9 gems' RBSs now installed. # Type checking files: ............................................................................................................................ ............................................. No type error detected. No untyped calls detected. => Success
  19. 4.2.1 bin/test でやっていること • テストのセットアップ • _test ディレクトリのクリーンアップ • 依存する

    gem・型のインストール • Steep のセットアップ • rbs validate • steep check • 最終的な返り値が untyped でないかのチェック
  20. 4.2.2. manifest.yaml .gem_rbs_collection/roo/2.10/roo/excelx/sheet.rbs:49:74: [error] Cannot find type `::Date` Diagnostic ID:

    RBS::UnknownTypeName def row: (String | Integer row_number) -> Array[nil | Link | bool | ::Date | ::DateTime | ::Time | Float | Integer | String] ~~~~~~ .gem_rbs_collection/roo/2.10/roo/link.rbs:28:23: [error] Cannot find type `::URI::File` Diagnostic ID: RBS::UnknownTypeName def to_uri: () -> (::URI::File | ::URI::FTP | ::URI::HTTP | ::URI::HTTPS | ::URI::LDAP | ::URI::LDAPS ... ~~~~~~~~~~~ csv_test.rb:7:6: [error] UnexpectedError: /xxx/gem_rbs_collection/gems/roo/2.10/ _test/.gem_rbs_collection/roo/2.10/roo/csv.rbs:51:78...51:88: Could not find ::CSV::Row(RBS::NoTypeFoundError) ... 型がない?
  21. 4.2.2. manifest.yaml https://github.com/ruby/gem_rbs_collection/blob/257e560cb101c5369b56aef9f3fe2a0c8f3f7e8e/gems/roo/2.10/ manifest.yaml # manifest.yaml describes dependencies which do

    not appear in the gemspec. # If this gem includes such dependencies, comment-out the following lines and # declare the dependencies. # If all dependencies appear in the gemspec, you should remove this file. # dependencies: - name: date - name: forwardable - name: uri - name: csv - name: openssl gemspec に記載されていない依存を教えてあげる必要がある
  22. コミュニティ • ruby-jp の #types • https://ruby-jp.github.io/ • Asakusa-bashi.rbs •

    https://asakusa-bashi-rbs.connpass.com/ • Machida.rb(予定) • https://machidarb.connpass.com/
  23. アカウント(思いついた限り) • https://x.com/soutaro • https://x.com/mametter • https://x.com/p_ck_ • https://x.com/_ksss_ •

    https://x.com/kokuyouwind • https://x.com/tk0miya • https://x.com/sinsoku_listy • https://x.com/Little_Rubyist • https://x.com/euglena1215 • https://x.com/fugakkbn • https://x.com/joker1007 • https://x.com/buta_botti • https://x.com/nemunemu3desu • etc...