Sorbet::Eraser

Build Status Gem

Erase all traces of sorbet-runtime code.

Sorbet is a type checker for Ruby. To annotate types in your Ruby code, you use constructs like sig and T.let. Sorbet then uses a static analysis tool to check that your code is type safe. At runtime, these types are enforced by the sorbet-runtime gem that provides implementations of all of these constructs.

Sometimes, you want to use Sorbet for development, but don't want to run sorbet-runtime in production. This may be because you have a performance-critical application, or because you're writing a library and you don't want to impose a runtime dependency on your users.

To handle these use cases, sorbet-eraser provides a way to erase all traces of sorbet-runtime code from your source code. This means that you can use Sorbet for development, but not have to worry about sorbet-runtime in production. For example,

# typed: true

class HelloWorld
  extend T::Sig

  sig { returns(String) }
  def hello
    T.let("World!", String)
  end
end

will be transformed into

#            

class HelloWorld
  extend T::Sig


  def hello
         ("World!"        )
  end
end

The sig method calls have been removed from your source code. T::Sig has been left in place, but is shimmed with an empty module to ensure any reflection is consistent. All line and column information has been preserved 1:1, so that stack traces and tracepoints will still be accurate.

Installation

Add this line to your application's Gemfile:

gem "sorbet-eraser"

And then execute:

$ bundle

Or install it yourself as:

$ gem install sorbet-eraser

Usage

There are two ways to use this gem, depending on your needs. You can erase sorbet-runtime code ahead of time or just in time.

Ahead of time

To erase sorbet-runtime code ahead of time, you would either use the CLI provided with this gem or the Ruby API. With the CLI, you would run:

bundle exec sorbet-eraser '**/*.rb'

It accepts any number of filepaths/patterns on the command line and will modify the source files in place with their erased contents. If you would instead prefer to script it yourself using the Ruby API, you would run:

Sorbet::Eraser.erase(source)

where source is a string that represents valid Ruby code.

Just in time

If you're looking to avoid a build step like the one described above, you can instead erase your code immediately before it is compiled by the Ruby virtual machine. To do, call:

require "sorbet/eraser/autoload"

as soon as possible when your application is first booting. This will hook into the autoload process to erase all sorbet-runtime code before it gets passed to Ruby to parse. Note that the tradeoff here is that it eliminates the need for a build step, but slows down your parse/boot time.

Runtime structures

If you used any runtime structures like T::Struct or T::Enum you'll need a runtime shim. We provide very basic versions of these in the sorbet-eraser gem, and they are required automatically.

Status

Below is a table of the status of each sorbet-runtime construct and its current support status.

Construct Status Replacement
# typed: foo #
extend T::* Shimmed
abstract!, final!, interface!, sealed! Shimmed
mixes_in_class_methods(*), requires_ancestor(*) Shimmed
type_member(*), type_template(*) Shimmed
class Foo < T::Enum Shimmed
class Foo < T::InexactStruct 🛠 Shimmed
class Foo < T::Struct 🛠 Shimmed
class Foo < T::ImmutableStruct 🛠 Shimmed
include T::Props 🛠 Shimmed
include T::Props::Serializable 🛠 Shimmed
include T::Props::Constructor 🛠 Shimmed
sig Removed
T.absurd(foo) Shimmed
T.assert_type!(foo, bar) foo
T.bind(self, foo) self
T.cast(foo, bar) foo
T.let(foo, bar) foo
T.must(foo) foo
T.reveal_type(foo) foo
T.type_alias { foo } Shimmed
T.unsafe(foo) foo

In the above table, for Status:

  • ✅ means that we are confident this is replaced 1:1.
  • 🛠 means there may be APIs that are not entirely supported. If you run into something that is missing, please open an issue.

In the above table, for Replacement:

  • Shimmed means that this gem provides a replacement module that will simply do nothing when its respective methods are called. We do this in order to maintain the same interface in the case that someone is doing runtime reflection. Also because anything that is shimmed will not be called that much/will not be in a hot path so performance is not really a consideration for those cases.
  • Removed means that the construct is removed entirely from the source.
  • Anything else means that the inputted code is replaced with that output.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/kddnewton/sorbet-eraser.

License

The gem is available as open source under the terms of the MIT License.