DEV Community

Masataka Pocke Kuwabara
Masataka Pocke Kuwabara

Posted on

rbs collection was released!

Recently we released RBS v1.6 with rbs collection feature which is "Bundler" for RBS! The feature is experimental now, and it will be GA in RBS v2 for Ruby 3.1. Now you can use it with gem install rbs if you use Ruby 2.6+!

I'm an RBS maintainer and I designed and implemented the feature, so I'll describe rbs collection feature in this article.

By the way, I made a presentation on Sep. 10 at RubyKaigi Takeout 2021 and explained rbs collection in the talk. This article is based on the talk. See https://github.com/pocke/rubykaigi-2021 for more details (unfortunately I spoke in Japanese but it has English slides and subtitles).

What is RBS

First of all, I introduce RBS.

RBS is a language to describe Ruby types. It is similar to .d.ts of TypeScript or header files of C-language.
The syntax is similar to Ruby.

The following code is a simple RBS example:

class Blog
  attr_reader name: String

  def initialize: (String name) -> void

  def each_article: () { (Article) -> void } -> self # For blog.each_article { |article| ... }
                  | () -> Enumerator[Article, self]  # For blog.each_article.map { |article| ... }
end

class Article
  attr_reader title: String
  attr_reader content: String

  def initialize: (String title, String, content) -> void

  def published_at: () -> Time
end
Enter fullscreen mode Exit fullscreen mode

ruby/gem_rbs_collection

I need to describe another topic before describing rbs collection. It is ruby/gem_rbs_collection GitHub repository.

The repository stores third-party gems' RBSs, like DefinitelyTyped.

rbs collection feature integrates this repository and tools use RBS, such as rbs command, Steep, and TypeProf.

rbs collection

It's the main topic of this article💪

rbs collection manages third-party RBSs, for example, installing, updating, and loading.

The first section describes a motivating example of rbs collection.
Before rbs collection, managing third-party RBSs is not efficient.

The second section describes how rbs collection solves the problem.

The last section describes known issues of rbs collection.
The feature still has some known issues for now, and I'll solve the issues until RBS v2.

Motivating example

In this section, I describe how to set up third-party RBSs for a Rails app without rbs collection as the motivating example.

How to set up

First, download ruby/gem_rbs_collection repository as a git submodule.

$ git submodule add https://github.com/ruby/gem_rbs_collection.git gem_rbs_collection
Enter fullscreen mode Exit fullscreen mode

Then rbs command is available but it needs many options. For example, the following command is necessary to validate RBSs.

# too many options ¯\_(ツ)_/¯
$ rbs -rlogger -rpathname -rmutex_m -rdate -rmonitor \
      -rsingleton -rtsort -rtime -rrack \
      -ractivesupport -ractionpack -ractivejob \
      -ractivemodel -ractionview -ractiverecord \
      -rrailties \
      --repo=gem_rbs_collection validate

# TypeProf also needs the same options.
$ typeprof -rlogger -rpathname -rmutex_m -rdate -rmonitor \
           -rsingleton -rtsort -rtime -rrack \
           -ractivesupport -ractionpack -ractivejob \
           -ractivemodel -ractionview -ractiverecord \
           -rrailties \
           --repo=gem_rbs_collection validate
Enter fullscreen mode Exit fullscreen mode

-r options specify library names you want to load, and --repo option specifies the repository path which is the git submodule.

And Steep needs a different configuration to load the same RBSs.

# Steepfile

target :app do
  # Specify a directory that contains target Ruby files to type check.
  check 'app'
  # Specify the git submoduled directory
  repo_path "gem_rbs_collection"
  # Specify the all library names to load.
  library 'pathname', 'logger',  'mutex_m', 'date',
          'monitor', 'singleton', 'tsort', 'time', 'rack',
          'activesupport', 'actionpack', 'activejob',
          'activemodel', 'actionview', 'activerecord',
          'railties'
end
Enter fullscreen mode Exit fullscreen mode

Many options are required because rbs, TypeProf, and Steep don't know which libraries are required.

Motivations

As you can see, managing third-party RBSs has problems. I explain three problems.

First, we need to resolve dependencies by hand. In the above examples, human resolved "my app depends on Active Support, then Active Support depends on Logger, and ...". It is too hard for humans 😂

Second, we need git submodule or copying RBSs to download third-party RBSs. It's too primitive. If you want to use different revisions RBSs, you can't use submodule but copying RBSs looks a hell.
For example, you want to use gem X RBS with revision R, but want to use gem Y RBS with revision R', git submodule doesn't work.

Third, we need to use different ways between RBS, Steep, and TypeProf to load RBSs. rbs command and TypeProf need to specify library names as command-line options, but Steep needs to specify them in Steepfile. It is not a single source so it is hard to maintain.

How rbs collection solves the problem

rbs collection automatically resolves dependencies, downloads RBSs, and loads RBSs from the single source. It means rbs collection sovles these problems!

To use rbs collection, you need to install rbs gem v1.6+. Then initialize the configuration file with rbs collection init command.

# Install rbs gem v1.6+
$ gem install rbs

# Initialize the config file
$ rbs collection init
warning: rbs collection is experimental, and the behavior may change until RBS v2.0
created: rbs_collection.yaml
Enter fullscreen mode Exit fullscreen mode

After that, edit the generated configuration file, rbs_collection.yaml. The full content is the following:
Note that I'll describe why you need to edit it in the "Known Issues" section.

# Download sources
sources:
  - name: ruby/gem_rbs_collection
    remote: https://github.com/ruby/gem_rbs_collection.git
    revision: main
    repo_dir: gems

# A directory to install the downloaded RBSs
path: .gem_rbs_collection

gems:
  # Skip loading rbs gem's RBS.
  # It's unnecessary if you don't use rbs as a library.
  - name: rbs
    ignore: true
  # 👮👮👮 Add the following lines
  - name: pathname
  - name: logger
  - name: mutex_m
  - name: date
  - name: monitor
  - name: singleton
  - name: tsort
  - name: time
  - name: set
Enter fullscreen mode Exit fullscreen mode

Then, execute rbs collection install command.

# rbs collection uses Gemfile.lock to resolve dependency,
# so execute `bundle install` before that to generate Gemfile.lock.
$ bundle install

# Install RBSs from ruby/gem_rbs_collection repo
$ rbs collection install
Enter fullscreen mode Exit fullscreen mode

Finally, the CLI tools are available!

You can use rbs and TypeProf commands without options.

$ rbs validate
$ typeprof target.rb
Enter fullscreen mode Exit fullscreen mode

You can use Steep with a few configurations.

# Steepfile

target :app do
  # Specify a directory that contains target Ruby files to type check.
  check 'app'
end
Enter fullscreen mode Exit fullscreen mode

Note that the rbs collection integrations with Steep and TypeProf have not been released yet now (2021-09-18). You need to install them from GitHub if you want to try them for now.
Steep integration was implemented in https://github.com/soutaro/steep/pull/420 but not yet released.
TypeProf integration was implemented in https://github.com/ruby/typeprof/pull/53 but not yet merged.

The mechanism of rbs collection install

rbs collection install command works with the following steps.

  1. Generating rbs_collection.lock.yaml
    1. Resolve the dependencies with Gemfile.lock.
    2. Write the gems to the lockfile, rbs_collection.lock.yaml.
  2. Installing RBSs
    1. Copy RBSs from ruby/gem_rbs_collection with the lockfile.

Then, rbs, steep and typeprof commands load the installed RBSs.

Known Issues

Unfortunately rbs collection has known issues.

Resolving Standard Libraries' Dependencies

rbs collection doesn't resolve dependencies of most standard libraries. For example, logger, pathname, etc.

In many cases, standard libraries do not appear in Gemfile.lock so rbs collection can't find them from Gemfile.lock.
We can do require stdlibs even if the stdlib is not included in gemspec or Gemfile. So most gems don't have explicit dependencies to stdlibs. For example, rails gem depends on many stdlibs such as pathname, but the gemspec doesn't include pathname.

I'll fix the problem by introducing a configuration file for RBS, such as one called manifest.yaml. The file will contain all stdlib dependency names.
For example, if foo gem depends on pathname and logger, the manifest.yaml will be the following.

# ruby/gem_rbs_collection/gems/foo/1.0/manifest.yaml

depends_on:
  - name: pathname
  - name: logger
Enter fullscreen mode Exit fullscreen mode

Other issues

rbs collection has other known issues. See https://github.com/ruby/rbs/pull/589 and https://github.com/ruby/rbs/issues/794 for more information.

Conclusion

I introduced rbs collection, which is the "Bundler" for RBS.
You can easily manage third party RBSs with the command.

I hope you will give it a try. Feel free to open issues if you have any problems!
Thank you for reading <3

Discussion (0)