Type Checking With Sorbet

The majority of the code in Homebrew is written in Ruby which is a dynamic language. To avail the benefits of static type checking, we have set up Sorbet in our codebase which provides the benefits of static type checking to dynamic languages like Ruby.

The Sorbet Documentation is a good place to get started if you want to dive deeper into Sorbet and it’s abilities.

Sorbet in the Homebrew Codebase

Inline Type Annotations

To add type annotations to a class or module, we need to first extend it with the T::Sig module (read this as Type::Signature). This adds the sig method which is used to annotate method signatures. Here’s a simple example:

class MyClass
  extend T::Sig

  sig { params(name: String).returns(String) }
  def my_method(name)
    "Hello, #{name}!"
  end
end

With params, we specify that we have a parameter name which must be a String and with returns, we specify that this method always returns a String.

For more information on how to express more complex types, refer to the official documentation:

Ruby Interface Files (.rbi)

RBI files help Sorbet learn about constants, ancestors and methods defined in ways it doesn’t understand natively. We can also create a RBI file to help Sorbet understand dynamic definitions.

Sometimes it is necessary to explicitly include the Kernel module in order for Sorbet to know that methods such as puts are available in a given context. This is mostly necessary for modules since they can be used in both BasicObjects (which don’t include Kernel) and Objects (which include Kernel by default). In this case, it is necessary to create an .rbi file (example) since re-including the Kernel module in actual code can break things.

Read more about RBI files here.

The Library/Homebrew/sorbet Directory

Using brew typecheck

When run without any arguments, brew typecheck, will run considering the strictness levels set in each of the individual Ruby files in the core Homebrew codebase. However, when it is run on a specific file or directory, more errors may show up since Sorbet cannot resolve constants defined outside the scope of the specified file. These problems can be solved with RBI files. Currently brew typecheck provides --quiet, --file, --dir and --ignore options but you can explore more options with srb tc --help and passing them with srb tc.

Resolving Type Errors

Sorbet reports type errors along with an error reference code, which can be used to look up more information on how to debug the error, or what causes the error in the Sorbet Documentation. Here is how to debug some common type errors:

Fork me on GitHub