Giter Site home page Giter Site logo

tapioca's Introduction

⚠️ Note: This software is currently under active development. The API and interface should be considered unstable until a v1.0.0 release.

Tapioca logo

Tapioca - The swiss army knife of RBI generation

Build Status

Tapioca makes it easy to work with Sorbet in your codebase. It surfaces types and methods from many sources that Sorbet cannot otherwise see – such as gems, Rails and other DSLs – compiles them into RBI files and makes it easy for you to add gradual typing to your application.

Features:

  • Easy installation and configuration
  • Generation of RBI files for the gems used in your application
    • Automatic generation from your application's Gemfile
    • Importing of signatures from the source code of gems
    • Importing of documentation from the source code of gems
    • Synchronization validation for your CI
  • Generation of RBI files for various DSL patterns that relies on meta-programming
    • Automatic generation from your application's content
    • Support many DSL patterns such as Rails, Google Protobuf, SmartProperties and more out of the box
    • Extensible interface that allows you to write your own DSL compilers for other DSL patterns
    • Automatic generation of signatures for methods from known DSLs
    • Synchronization validation for your CI
  • Management of shim RBI files
    • Find useless definitions in shim RBI files from gems generated RBI files
    • Find useless definitions in shim RBI files from DSL generated RBI files
    • Find useless definitions in shim RBI files from Sorbet's embedded RBI for core and stdlib
    • Synchronization validation for your CI

Table of Contents

Installation

Add this line to your application's Gemfile:

group :development, :test do
  gem 'tapioca', require: false
end

Run bundle install and make sure Tapioca is properly installed:

$ tapioca help

Commands:
  tapioca --version, -v      # Show version
  tapioca annotations        # Pull gem RBI annotations from remote sources
  tapioca check-shims        # Check duplicated definitions in shim RBIs
  tapioca configure          # Initialize folder structure and type checking configuration
  tapioca dsl [constant...]  # Generate RBIs for dynamic methods
  tapioca gem [gem...]       # Generate RBIs from gems
  tapioca help [COMMAND]     # Describe available commands or one specific command
  tapioca init               # Get project ready for type checking
  tapioca require            # Generate the list of files to be required by tapioca
  tapioca todo               # Generate the list of unresolved constants

Options:
  -c, [--config=<config file path>]                  # Path to the Tapioca configuration file
                                                     # Default: sorbet/tapioca/config.yml
  -V, [--verbose], [--no-verbose], [--skip-verbose]  # Verbose output for debugging purposes
                                                     # Default: false

Getting started

Execute this command to get started:

$ bundle exec tapioca init

This will:

  1. create the configuration file for Sorbet, the configuration file for Tapioca and the require.rb file
  2. install the binstub for Tapioca in your app's bin/ folder, so that you can use bin/tapioca to run commands in your app
  3. pull the community RBI annotations from the central repository matching your app's gems
  4. generate the RBIs for your app's gems
  5. generate the RBI file for missing constants

See the following sections for more details about each step.

$ tapioca help init

Usage:
  tapioca init

Options:
  -c, [--config=<config file path>]                  # Path to the Tapioca configuration file
                                                     # Default: sorbet/tapioca/config.yml
  -V, [--verbose], [--no-verbose], [--skip-verbose]  # Verbose output for debugging purposes
                                                     # Default: false

Get project ready for type checking

Usage

Generating RBI files for gems

Sorbet does not read the code in your gem dependencies, so it does not know the constants and methods declared inside gems. Tapioca is able to load your gem dependencies from your application's Gemfile and compile RBI files to represent their content.

In order to generate the RBI files for the gems used in your application, run the following command:

$ bin/tapioca gems [gems...]

Removing RBI files of gems that have been removed:

  Nothing to do.

Generating RBI files of gems that are added or updated:

  Requiring all gems to prepare for compiling...    Done

  Compiled ansi
      create  sorbet/rbi/gems/[email protected]

  ...

All operations performed in working directory.
Please review changes and commit them.

This will load your application, find all the gems required by it and generate an RBI file for each gem under the sorbet/rbi/gems directory for each of those gems. This process will also import signatures that can be found inside each gem sources, and, optionally, any YARD documentation inside the gem.

$ tapioca help gem

Usage:
  tapioca gem [gem...]

Options:
  --out, -o,   [--outdir=directory]                                                                  # The output directory for generated gem RBI files
                                                                                                     # Default: sorbet/rbi/gems
               [--file-header], [--no-file-header], [--skip-file-header]                             # Add a "This file is generated" header on top of each generated RBI file
                                                                                                     # Default: true
               [--all], [--no-all], [--skip-all]                                                     # Regenerate RBI files for all gems
                                                                                                     # Default: false
  --pre, -b,   [--prerequire=file]                                                                   # A file to be required before Bundler.require is called
  --post, -a,  [--postrequire=file]                                                                  # A file to be required after Bundler.require is called
                                                                                                     # Default: sorbet/tapioca/require.rb
  -x,          [--exclude=gem [gem ...]]                                                             # Exclude the given gem(s) from RBI generation
               [--include-dependencies], [--no-include-dependencies], [--skip-include-dependencies]  # Generate RBI files for dependencies of the given gem(s)
                                                                                                     # Default: false
  --typed, -t, [--typed-overrides=gem:level [gem:level ...]]                                         # Override for typed sigils for generated gem RBIs
                                                                                                     # Default: {"activesupport"=>"false"}
               [--verify], [--no-verify], [--skip-verify]                                            # Verify RBIs are up-to-date
                                                                                                     # Default: false
               [--doc], [--no-doc], [--skip-doc]                                                     # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
                                                                                                     # Default: true
               [--loc], [--no-loc], [--skip-loc]                                                     # Include comments with source location when generating RBIs
                                                                                                     # Default: true
               [--exported-gem-rbis], [--no-exported-gem-rbis], [--skip-exported-gem-rbis]           # Include RBIs found in the `rbi/` directory of the gem
                                                                                                     # Default: true
  -w,          [--workers=N]                                                                         # Number of parallel workers to use when generating RBIs (default: auto)
               [--auto-strictness], [--no-auto-strictness], [--skip-auto-strictness]                 # Autocorrect strictness in gem RBIs in case of conflict with the DSL RBIs
                                                                                                     # Default: true
  --dsl-dir,   [--dsl-dir=directory]                                                                 # The DSL directory used to correct gems strictnesses
                                                                                                     # Default: sorbet/rbi/dsl
               [--rbi-max-line-length=N]                                                             # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
                                                                                                     # Default: 120
  -e,          [--environment=ENVIRONMENT]                                                           # The Rack/Rails environment to use when generating RBIs
                                                                                                     # Default: development
               [--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error]  # Halt upon a load error while loading the Rails application
                                                                                                     # Default: true
  -c,          [--config=<config file path>]                                                         # Path to the Tapioca configuration file
                                                                                                     # Default: sorbet/tapioca/config.yml
  -V,          [--verbose], [--no-verbose], [--skip-verbose]                                         # Verbose output for debugging purposes
                                                                                                     # Default: false

Generate RBIs from gems

By default, running tapioca gem will only generate the RBI files for gems that have been added to or removed from the project's Gemfile this means that Tapioca will not regenerate the RBI files for untouched gems. If you want to force the regeneration you can supply gem names to the tapioca gem command. When supplying gem names if you want to generate RBI files for their dependencies as well, you can use the --include-dependencies option. When changing Tapioca configuration or bumping its version, it may be useful to force the regeneration of all the RBI files previously generated. This can be done with the --all option:

bin/tapioca gems --all

Are you coming from srb rbi? See how tapioca gem compares to srb rbi.

Manually requiring parts of a gem

It may happen that the RBI file generated for a gem listed inside your Gemfile.lock is missing some definitions that you would expect it to be exporting.

For gems that have a normal default require and that load all of their constants through that, everything should work seamlessly. However, for gems that are marked as require: false in the Gemfile, or for gems that export constants optionally via different requires, where a single require does not load the whole gem code into memory, Tapioca will not be able to load some of the types into memory and, thus, won't be able to generate complete RBIs for them. For this reason, we need to keep a small external file named sorbet/tapioca/require.rb that is executed after all the gems in the Gemfile have been required and before generation of gem RBIs have started. This file is responsible for adding the requires for additional files from gems, which are not covered by the default require.

For example, suppose you are using the class BetterHtml::Parser exported from the better_html gem. Just doing a require "better_html" (which is the default require) does not load that type:

$ bundle exec irb

irb(main):001> require 'better_html'
=> true
irb(main):002> BetterHtml
=> BetterHtml
irb(main):003> BetterHtml::Parser
(irb):3:in '<main>': uninitialized constant BetterHtml::Parser (NameError)
Did you mean?  BetterHtml::ParserError
irb(main):004> require 'better_html/parser'
=> true
irb(main):005> BetterHtml::Parser
=> BetterHtml::Parser

In order to make sure that tapioca can reflect on that type, we need to add the line require "better_html/parser" to the sorbet/tapioca/require.rb file. This will make sure BetterHtml::Parser is loaded into memory and a type annotation is generated for it in the better_html.rbi file. If this extra require line is not added to sorbet/tapioca/require.rb file, then Tapioca will be able to generate definitions for BetterHtml and other constants, but not for BetterHtml::Parser, which will be missing from the RBI file.

For example, you can take a look at Tapioca's own require.rb file:

# typed: strict
# frozen_string_literal: true

require "ansi/code"
require "google/protobuf"
require "rails/all"
require "rails/generators"
require "rails/generators/app_base"
require "rake/testtask"
require "rubocop/rake_task"

If you ever run into a case, where you add a gem or update the version of a gem and run tapioca gem but don't have some types you expect in the generated gem RBI files, you will need to make sure you have added the necessary requires to the sorbet/tapioca/require.rb file and regenerate the RBI file for that gem explicitly using bin/tapioca gem <gem-name>.

To help you get started, you can use the command tapioca require to auto-populate the contents of the sorbet/tapioca/require.rb file with all the requires found in your application:

$ bin/tapioca require

Compiling sorbet/tapioca/require.rb, this may take a few seconds... Done

All requires from this application have been written to sorbet/tapioca/require.rb.
Please review changes and commit them, then run `bin/tapioca gem`.

Once the file is generated, you should review it, remove all unnecessary requires and commit it.

Excluding a gem from RBI generation

It may be useful to exclude some gems from the generation process. For example for gems that are in Bundle's debug group or gems of which the contents are dependent on the architecture they are loaded on.

To do so you can pass the list of gems you want to exclude in the command line with the --exclude option:

$ bin/tapioca gems --exclude gemA gemB

Or through the configuration file:

gem:
  exclude:
    - gemA
    - gemB

There are a few development/test environment gems that can cause RBI generation issues, so Tapioca skips them by default:

  • debug
  • fakefs

Changing the strictness level of the RBI for a gem

By default, all RBI files for gems are generated with the strictness level typed: true. Sometimes, this strictness level can create type-checking errors when a gem contains definitions that conflict with Sorbet internal definitions for Ruby core and standard library.

Tapioca comes with an automatic detection (option --auto-strictness, enabled by default) of such cases and will switch the strictness level to typed: false in RBI files containing conflicts with the core and standard library definitions. It is nonetheless possible to manually switch the strictness level for a gem using the --typed-overrides option:

$ bin/tapioca gems --typed-overrides gemA:false gemB:false

Or through the configuration file:

gem:
  typed_overrides:
    gemA: "false"
    gemB: "false"

Keeping RBI files for gems up-to-date

To ensure all RBI files for gems are present and have the correct version based on your Gemfile.lock, Tapioca provides a --verify option:

$ bin/tapioca gems --verify

Checking for out-of-date RBIs...

Nothing to do, all RBIs are up-to-date.

This option can be used in CI to make sure the RBI files are up-to-date and ensure accurate type checking.

Warning: doing so will break your normal automated dependency update workflow as every pull request opened to bump a gem version will fail CI since the RBI will be out-of-date. You will need to either set up additional automation (eg Dependabot), or manually run bin/tapioca gems and commit the results.

Warning: Verification ONLY ensures the RBI files are present, used and have the correct version based on the gem version in your Gemfile.lock. It's possible for your RBIs to be out-of-date if RBIs were not regenerated following an update to tapioca itself or if a another gem that injects functionality (e.g. turbo-rails) was installed/updated/removed. To ensure RBIs are completely up-to-date, you must run bin/tapioca gems --all but it's not recommended to do this in CI as it's an expensive operation.

Importing hand written signatures from gem's rbi/ folder

Tapioca will import any signatures found in the rbi/ folder of a given gem and combine them with the RBIs it generates. This is useful when a gem doesn't want to depend on sorbet-runtime but still wants to provide type safety to users during static checks. Note that the rbi/ folder needs to be included in the gem release using the .gemspec file. Applications can choose not to import these signatures using the --no-exported-gem-rbis flag.

Pulling RBI annotations from remote sources

Since Tapioca does not perform any type inference, the RBI files generated for the gems do not contain any type signatures. Instead, Tapioca relies on the community to provide high-quality, manually written RBI annotations for public gems.

To pull the annotations relevant to your project from the central repository, run the annotations command:

$ bin/tapioca annotations

Retrieving index from central repository... Done
Listing gems from Gemfile.lock... Done
Removing annotations for gems that have been removed...  Nothing to do
Fetching gem annotations from central repository...

  Fetched activesupport
   created  sorbet/rbi/annotations/activesupport.rbi

Done
$ tapioca help annotations

Usage:
  tapioca annotations

Options:
               [--sources=one two three]                      # URIs of the sources to pull gem RBI annotations from
                                                              # Default: "https://raw.githubusercontent.com/Shopify/rbi-central/main"
               [--netrc], [--no-netrc], [--skip-netrc]        # Use .netrc to authenticate to private sources
                                                              # Default: true
               [--netrc-file=NETRC_FILE]                      # Path to .netrc file
               [--auth=AUTH]                                  # HTTP authorization header for private sources
  --typed, -t, [--typed-overrides=gem:level [gem:level ...]]  # Override for typed sigils for pulled annotations
  -c,          [--config=<config file path>]                  # Path to the Tapioca configuration file
                                                              # Default: sorbet/tapioca/config.yml
  -V,          [--verbose], [--no-verbose], [--skip-verbose]  # Verbose output for debugging purposes
                                                              # Default: false

Pull gem RBI annotations from remote sources

By default, Tapioca will pull the annotations stored in the central repository located at https://github.com/Shopify/rbi-central. It is possible to use a custom repository by changing the value of the --sources options. For example if your repository is stored on Github:

$ bin/tapioca annotations --sources https://raw.githubusercontent.com/$USER/$REPO/$BRANCH

Tapioca also supports pulling annotations from multiple sources:

$ bin/tapioca annotations --sources https://raw.githubusercontent.com/$USER/$REPO1/$BRANCH https://raw.githubusercontent.com/$USER/$REPO2/$BRANCH

Basic authentication

Private repositories can be used as sources by passing the option --auth with an authentication string. For Github, this string is token $TOKEN where $TOKEN is a personal access token:

$ bin/tapioca annotations --sources https://raw.githubusercontent.com/$USER/$PRIVATE_REPO/$BRANCH --auth "token $TOKEN"

Using a .netrc file

Tapioca supports reading credentials from a netrc file (defaulting to ~/.netrc).

Given these lines in your netrc:

machine raw.githubusercontent.com
  login $USERNAME
  password $TOKEN

where $USERNAME is your Github username and $TOKEN is a personal access token, then, if you run Tapioca with the --netrc option (enabled by default), your annotation requests should be authenticated properly.

The --netrc-file option can be specified to read from a file other than ~/.netrc:

$ bin/tapioca annotations --netrc-file /path/to/my/netrc/file

Similar to --netrc-file, you can also specify an alternative netrc file by using the TAPIOCA_NETRC_FILE environment variable:

$ TAPIOCA_NETRC_FILE=/path/to/my/netrc/file bin/tapioca annotations

Tapioca will first try to find the netrc file as specified by the --netrc-file option. If that option is not supplied, it will try the TAPIOCA_NETRC_FILE environment variable value. If that value is not supplied either, it will fallback to ~/.netrc.

Changing the typed strictness of annotations files

Sometimes the annotations files pulled by Tapioca will create type errors in your project because of incompatibilities. It is possible to ignore such files by switching their strictness level --typed-overrides option:

$ bin/tapioca annotations --typed-overrides gemA:ignore gemB:false

Or through the configuration file:

annotations:
  typed_overrides:
    gemA: "ignore"
    gemB: "false"

Generating RBI files for Rails and other DSLs

Sorbet by itself does not understand DSLs involving meta-programming, such as Rails. This means that Sorbet won't know about constants and methods generated by ActiveRecord or ActiveSupport. To solve this, Tapioca can load your application and introspect it to find the constants and methods that would exist at runtime and compile them into RBI files.

To generate the RBI files for the DSLs used in your application, run the following command:

$ bin/tapioca dsl

Loading Rails application... Done
Loading DSL compiler classes... Done
Compiling DSL RBI files...

      create  sorbet/rbi/dsl/my_model.rbi
      ...

Done

This will generate DSL RBIs for specified constants (or for all handled constants, if a constant name is not supplied). You can read about DSL RBI compilers supplied by tapioca in the manual.

$ tapioca help dsl

Usage:
  tapioca dsl [constant...]

Options:
  --out, -o, [--outdir=directory]                                                                  # The output directory for generated DSL RBI files
                                                                                                   # Default: sorbet/rbi/dsl
             [--file-header], [--no-file-header], [--skip-file-header]                             # Add a "This file is generated" header on top of each generated RBI file
                                                                                                   # Default: true
             [--only=compiler [compiler ...]]                                                      # Only run supplied DSL compiler(s)
             [--exclude=compiler [compiler ...]]                                                   # Exclude supplied DSL compiler(s)
             [--verify], [--no-verify], [--skip-verify]                                            # Verifies RBIs are up-to-date
                                                                                                   # Default: false
  -q,        [--quiet], [--no-quiet], [--skip-quiet]                                               # Suppresses file creation output
                                                                                                   # Default: false
  -w,        [--workers=N]                                                                         # Number of parallel workers to use when generating RBIs (default: 2)
                                                                                                   # Default: 2
             [--rbi-max-line-length=N]                                                             # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
                                                                                                   # Default: 120
  -e,        [--environment=ENVIRONMENT]                                                           # The Rack/Rails environment to use when generating RBIs
                                                                                                   # Default: development
  -l,        [--list-compilers], [--no-list-compilers], [--skip-list-compilers]                    # List all loaded compilers
                                                                                                   # Default: false
             [--app-root=APP_ROOT]                                                                 # The path to the Rails application
                                                                                                   # Default: .
             [--halt-upon-load-error], [--no-halt-upon-load-error], [--skip-halt-upon-load-error]  # Halt upon a load error while loading the Rails application
                                                                                                   # Default: true
             [--skip-constant=constant [constant ...]]                                             # Do not generate RBI definitions for the given application constant(s)
  -c,        [--config=<config file path>]                                                         # Path to the Tapioca configuration file
                                                                                                   # Default: sorbet/tapioca/config.yml
  -V,        [--verbose], [--no-verbose], [--skip-verbose]                                         # Verbose output for debugging purposes
                                                                                                   # Default: false

Generate RBIs for dynamic methods

Keeping RBI files for DSLs up-to-date

To ensure all RBI files for DSLs are up-to-date with the latest changes in your application or database, Tapioca provide a --verify option:

$ bin/tapioca dsl --verify

Loading Rails application... Done
Loading DSL compiler classes... Done
Checking for out-of-date RBIs...


RBI files are out-of-date. In your development environment, please run:
  `bin/tapioca dsl`
Once it is complete, be sure to commit and push any changes

Reason:
  File(s) changed:
  - sorbet/rbi/dsl/my_model.rbi

This option can be used on CI to make sure the RBI files are always up-to-date and ensure accurate type checking.

If you are using Rails, you can configure tapioca dsl to run after each migration:

# Rakefile
if Rails.env.development?
  namespace :db do
    task :migrate do # Appends to the existing `db:migrate` task
      system("bundle exec tapioca dsl", exception: true)
    end
  end

Writing custom DSL compilers

It is possible to create your own compilers for DSLs not supported by Tapioca out of the box.

Let's take for example this Encryptable module that uses the included hook to dynamically add a few methods to the classes that include it:

module Encryptable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def attr_encrypted(attr_name)
      encrypted_attributes << attr_name

      attr_accessor(attr_name)

      encrypted_attr_name = :"#{attr_name}_encrypted"

      define_method(encrypted_attr_name) do
        value = send(attr_name)
        encrypt(value)
      end

      define_method("#{encrypted_attr_name}=") do |value|
        send("#{attr_name}=", decrypt(value))
      end
    end

    def encrypted_attributes
      @encrypted_attributes ||= []
    end
  end

  private

  def encrypt(value)
    value.unpack("H*").first
  end

  def decrypt(value)
    [value].pack("H*")
  end
end

When Encryptable is included in a class like this one, it makes it possible to call attr_encrypted to define an attribute, its accessors and its encrypted accessors:

class CreditCard
  include Encryptable

  attr_encrypted :number
end

These accessors can then be used on the CreditCard instance without having to define them in the class:

# typed: true
# file: example.rb

card = CreditCard.new
card.number = "1234 5678 9012 3456"

p card.number             # => "1234 5678 9012 3456"
p card.number_encrypted   # => "31323334203536373820393031322033343536"

card.number_encrypted = "31323334203536373820393031322033343536"
p card.number             # => "1234 5678 9012 3456"

Sadly, since these methods have been created dynamically at runtime, when our attr_encryptable method was run, there are no static traces of the number, number=, number_encrypted and number_encrypted= methods. Since Sorbet does not run the Ruby code but analyses it statically, it can't see these methods and running type-checking will show a bunch of errors:

$ bundle exec srb tc

lib/example.rb:5: Method number= does not exist on CreditCard https://srb.help/7003
lib/example.rb:7: Method number does not exist on CreditCard https://srb.help/7003
lib/example.rb:8: Method number_encrypted does not exist on CreditCard https://srb.help/7003
lib/example.rb:10: Method number_encrypted= does not exist on CreditCard https://srb.help/7003
lib/example.rb:11: Method number does not exist on CreditCard https://srb.help/7003

Errors: 5

To solve this you will have to create your own DSL compiler able that understands the Encryptable DSL and can generate the RBI definitions representing the actual shape of CreditCard at runtime.

To do so, you need to create a new DSL compiler similar to the following:

module Tapioca
  module Compilers
    class Encryptable < Tapioca::Dsl::Compiler
      extend T::Sig

      ConstantType = type_member {{ fixed: T.class_of(Encryptable) }}

      sig { override.returns(T::Enumerable[Module]) }
      def self.gather_constants
        # Collect all the classes that include Encryptable
        all_classes.select { |c| c < ::Encryptable }
      end

      sig { override.void }
      def decorate
        # Create a RBI definition for each class that includes Encryptable
        root.create_path(constant) do |klass|
          # For each encrypted attribute we find in the class
          constant.encrypted_attributes.each do |attr_name|
            # Create the RBI definitions for all the missing methods
            klass.create_method(attr_name, return_type: "String")
            klass.create_method("#{attr_name}=", parameters: [ create_param("value", type: "String") ], return_type: "void")
            klass.create_method("#{attr_name}_encrypted", return_type: "String")
            klass.create_method("#{attr_name}_encrypted=", parameters: [ create_param("value", type: "String") ], return_type: "void")
          end
        end
      end
    end
  end
end

In order for this DSL compiler to be discovered by Tapioca, it either needs to be placed inside the sorbet/tapioca/compilers directory of your application or be inside a tapioca/dsl/compilers folder on the load path. For example, if Encryptable was being exposed by a gem, all the gem needs to do is to place the DSL compiler inside the lib/tapioca/dsl/compilers folder and it will be automatically discovered and loaded by Tapioca.

There are two main parts to the DSL compiler API: gather_constants and decorate:

  • The gather_constants class method collects all classes (or modules) that should be processed by this specific DSL compiler.
  • The decorate method defines how to generate the necessary RBI definitions for the gathered constants.

Every compiler must declare the type member ConstantType in order for Sorbet to understand what the return type of the constant attribute reader is. It needs to be assigned the correct type variable matching the type of constants that gather_constants returns. This generic variable allows Sorbet to type-check method calls on the constant reader in your decorate method. See the Sorbet documentation on generics for more information.

You can now run the new RBI compiler through the normal DSL generation process (your custom compiler will be loaded automatically by Tapioca):

$ bin/tapioca dsl

Loading Rails application... Done
Loading DSL compiler classes... Done
Compiling DSL RBI files...

      create  sorbet/rbi/dsl/credit_card.rbi

Done

And then run Sorbet without error:

$ bundle exec srb tc

No errors! Great job.

For more concrete and advanced examples, take a look at Tapioca's default DSL compilers.

Writing custom DSL extensions

When writing custom DSL compilers, it is sometimes necessary to rely on an extension, i.e. a bit of code that is being loaded before the application in order to override some behavior. This is typically useful when a DSL's implementation does not store enough information for the compiler to properly define signatures.

Let's reuse the previous Encryptable module as an example, but this time let's imagine that the implementation of attr_encrypted does not store attribute names:

module Encryptable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def attr_encrypted(attr_name)
      attr_accessor(attr_name)

      encrypted_attr_name = :"#{attr_name}_encrypted"

      define_method(encrypted_attr_name) do
        value = send(attr_name)
        encrypt(value)
      end

      define_method("#{encrypted_attr_name}=") do |value|
        send("#{attr_name}=", decrypt(value))
      end
    end
  end

  private

  def encrypt(value)
    value.unpack("H*").first
  end

  def decrypt(value)
    [value].pack("H*")
  end
end

Without the attribute_names array, the compiler has no way of knowing which methods were defined by the attr_encrypted DSL. This can be solved by defining an extension that will override the behavior of attr_encrypted:

require "encryptable"

module Tapioca
  module Extensions
    module Encryptable
      attr_reader :__tapioca_encrypted_attributes

      def attr_encrypted(attr_name)
        @__tapioca_encrypted_attributes ||= []
        @__tapioca_encrypted_attributes << attr_name.to_s

        super
      end

      ::Encryptable::ClassMethods.prepend(self)
    end
  end
end

The compiler can now use the __tapioca_encrypted_attributes array managed by the extension:

module Tapioca
  module Compilers
    class Encryptable < Tapioca::Dsl::Compiler
      extend T::Sig

      ConstantType = type_member {{ fixed: T.class_of(Encryptable) }}

      sig { override.returns(T::Enumerable[Module]) }
      def self.gather_constants
        # Collect all the classes that include Encryptable
        all_classes.select { |c| c < ::Encryptable }
      end

      sig { override.void }
      def decorate
        # Create a RBI definition for each class that includes Encryptable
        root.create_path(constant) do |klass|
          # For each encrypted attribute we find in the class
          constant.__tapioca_encrypted_attributes.each do |attr_name|
            # Create the RBI definitions for all the missing methods
            klass.create_method(attr_name, return_type: "String")
            klass.create_method("#{attr_name}=", parameters: [ create_param("value", type: "String") ], return_type: "void")
            klass.create_method("#{attr_name}_encrypted", return_type: "String")
            klass.create_method("#{attr_name}_encrypted=", parameters: [ create_param("value", type: "String") ], return_type: "void")
          end
        end
      end
    end
  end
end

In order for DSL extensions to be discovered by Tapioca, they either needs to be placed inside the sorbet/tapioca/extensions directory of your application or be inside a tapioca/dsl/extensions folder on the load path.

For more concrete and advanced examples, take a look at Tapioca's default DSL extensions.

RBI files for missing constants and methods

Even after generating the RBIs, it is possible that some constants or methods are still undefined for Sorbet.

This might be for multiple reasons, with the most frequents ones being:

  • The constant or method comes from a part of the gem that Tapioca cannot load (optional dependency, wrong architecture, etc.)
  • The constant or method comes from a DSL or meta-programming that Tapioca doesn't support yet
  • The constant or method only exists when a specific code path is executed

The best way to deal with such occurrences is shims. A shim is a hand-crafted RBI file that tells Sorbet about constants, ancestors, methods, etc. that it can't understand statically and aren't already generated by Tapioca.

These shims are usually placed in the sorbet/rbi/shims directory. From there, conventionally, you should follow the directory structure of the project to the file you'd like to shim. For example, say you had a person.rb file found at app/models/person.rb. If you were to add a shim for it, you'd want to create your RBI file at sorbet/rbi/shims/app/models/person.rbi.

A shim might be as simple as the class definition with an empty method body as below:

# typed: true

class Person
  sig { void }
  def some_method_sorbet_cannot_find; end
end

As you migrate to newer versions of Sorbet or Tapioca, some shims may become useless as Sorbet's internal definitions for Ruby's core and standard library is enhanced or Tapioca is able to generate definitions for new DSLs. To avoid keeping outdated or useless definitions inside your application shims, Tapioca provides the check-shims command:

$ bin/tapioca check-shims

Loading Sorbet payload...  Done
Loading shim RBIs from sorbet/rbi/shims...  Done
Loading gem RBIs from sorbet/rbi/gems...  Done
Loading gem RBIs from sorbet/rbi/dsl...  Done
Loading annotation RBIs from sorbet/rbi/annotations...  Done
Looking for duplicates...  Done

Duplicated RBI for ::MyModel#title:
  * sorbet/rbi/shims/my_model.rbi:2:2-2:14
  * sorbet/rbi/dsl/my_model.rbi:2:2-2:14

Duplicated RBI for ::String#capitalize:
  * https://github.com/sorbet/sorbet/tree/master/rbi/core/string.rbi#L406
  * sorbet/rbi/shims/core/string.rbi:3:2-3:23

Please remove the duplicated definitions from the sorbet/rbi/shims directory.

This command can be used on CI to make sure the RBI shims are always up-to-date and non-redundant with generated files.

$ tapioca help check_shims

Usage:
  tapioca check-shims

Options:
      [--gem-rbi-dir=GEM_RBI_DIR]                    # Path to gem RBIs
                                                     # Default: sorbet/rbi/gems
      [--dsl-rbi-dir=DSL_RBI_DIR]                    # Path to DSL RBIs
                                                     # Default: sorbet/rbi/dsl
      [--shim-rbi-dir=SHIM_RBI_DIR]                  # Path to shim RBIs
                                                     # Default: sorbet/rbi/shims
      [--annotations-rbi-dir=ANNOTATIONS_RBI_DIR]    # Path to annotations RBIs
                                                     # Default: sorbet/rbi/annotations
      [--todo-rbi-file=TODO_RBI_FILE]                # Path to the generated todo RBI file
                                                     # Default: sorbet/rbi/todo.rbi
      [--payload], [--no-payload], [--skip-payload]  # Check shims against Sorbet's payload
                                                     # Default: true
  -w, [--workers=N]                                  # Number of parallel workers (default: auto)
  -c, [--config=<config file path>]                  # Path to the Tapioca configuration file
                                                     # Default: sorbet/tapioca/config.yml
  -V, [--verbose], [--no-verbose], [--skip-verbose]  # Verbose output for debugging purposes
                                                     # Default: false

Check duplicated definitions in shim RBIs

Depending on the amount of meta-programming used in your project this can mean an overwhelming amount of manual work. In this case, you should consider writing a custom DSL compiler.

Configuration

Tapioca supports loading command defaults from a configuration file. The default configuration file location is sorbet/tapioca/config.yml but this default can be changed using the --config flag and supplying an alternative configuration file path.

Tapioca's configuration file must be a well-formed YAML file with top-level keys for the various Tapioca commands. Keys under each such top-level command should be the underscore version of a long option name for that command and the value for that key should be the value of the option.

For example, if you always want to generate gem RBIs with inline documentation, then you would create the file sorbet/tapioca/config.yml as:

gem:
  doc: true

Additionally, if you always want to exclude the AASM and ActiveRecordFixtures DSL compilers in your DSL RBI generation runs, your config file would then look like this:

gem:
  doc: true
dsl:
  exclude:
  - UrlHelpers
  - ActiveRecordFixtures

The full configuration file, with each option and its default value, would look something like this:

---
require:
  postrequire: sorbet/tapioca/require.rb
todo:
  todo_file: sorbet/rbi/todo.rbi
  file_header: true
dsl:
  outdir: sorbet/rbi/dsl
  file_header: true
  only: []
  exclude: []
  verify: false
  quiet: false
  workers: 2
  rbi_max_line_length: 120
  environment: development
  list_compilers: false
  app_root: "."
  halt_upon_load_error: true
  skip_constant: []
gem:
  outdir: sorbet/rbi/gems
  file_header: true
  all: false
  prerequire: ''
  postrequire: sorbet/tapioca/require.rb
  exclude: []
  include_dependencies: false
  typed_overrides:
    activesupport: 'false'
  verify: false
  doc: true
  loc: true
  exported_gem_rbis: true
  workers: 1
  auto_strictness: true
  dsl_dir: sorbet/rbi/dsl
  rbi_max_line_length: 120
  environment: development
  halt_upon_load_error: true
check_shims:
  gem_rbi_dir: sorbet/rbi/gems
  dsl_rbi_dir: sorbet/rbi/dsl
  shim_rbi_dir: sorbet/rbi/shims
  annotations_rbi_dir: sorbet/rbi/annotations
  todo_rbi_file: sorbet/rbi/todo.rbi
  payload: true
  workers: 1
annotations:
  sources:
  - https://raw.githubusercontent.com/Shopify/rbi-central/main
  netrc: true
  netrc_file: ''
  typed_overrides: {}

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/tapioca. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

DSL compilers

Contributions to existing DSL compilers are welcome. However, new compilers that support DSLs for gems other than Rails should live outside of Tapioca. Please refer to writing custom dsl compilers for more information.

License

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

tapioca's People

Contributors

andyw8 avatar bdewater avatar bitwise-aiden avatar cjhutchi avatar dduugg avatar dependabot[bot] avatar dirceu avatar dougedey avatar egiurleo avatar etiennebarrie avatar fcheung avatar georgebrock avatar jeffcarbs avatar kaanozkan avatar katayounhillier avatar lte avatar mihyaeru21 avatar mojanjz avatar morriar avatar mutecipher avatar nathanmsmith avatar paracycle avatar rafaelfranca avatar ryanbrushett avatar ryanwilsonperkin avatar sambostock avatar st0012 avatar tobiasbales avatar vinistock avatar wildmaples avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tapioca's Issues

Issue with IO and `<internal:prelude>`

I've recently run into an issue where the symbol generator is trying to do File.realpath on <internal:prelude> and therefore crashing.

Can't exactly pinpoint what broke this behaviour, but it's now broken :( It may be because a gem is now updating/modifying parts of IO 🤷‍♂

Also just seen it with (eval) as the path, too. Can we maybe do a File.exists? or similar before File.realpath to make sure it is there instead of blowing up?

Below is the stack trace

Compiling hamster, this may take a few seconds... bundler: failed to load command: tapioca (/Users/david/.rbenv/versions/2.6.5/bin/tapioca)
Errno::ENOENT: No such file or directory @ realpath_rec - /Users/david/projects/ferocia/up/api/<internal:prelude>
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:429:in `realpath'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:429:in `path_in_gem?'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:465:in `method_in_gem?'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:389:in `compile_method'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:355:in `block (2 levels) in compile_directly_owned_methods'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:353:in `map'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:353:in `block in compile_directly_owned_methods'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:352:in `each'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:352:in `flat_map'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:352:in `compile_directly_owned_methods'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:336:in `compile_methods'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:177:in `block in compile_body'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:450:in `with_indentation'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:176:in `compile_body'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:162:in `compile_module'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:114:in `compile_constant'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:100:in `compile'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:74:in `generate_from_symbol'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:221:in `call'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:221:in `block in _on_method_added'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:32:in `map'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table/symbol_generator.rb:32:in `generate'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/compilers/symbol_table_compiler.rb:19:in `compile'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:285:in `compile_rbi'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:234:in `block (2 levels) in perform_additions'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:225:in `each'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:225:in `block in perform_additions'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor/shell/basic.rb:44:in `indent'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:219:in `perform_additions'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:74:in `sync_rbis_with_gemfile'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5478/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/cli.rb:63:in `block in sync'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca.rb:10:in `silence_warnings'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/cli.rb:62:in `sync'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor/command.rb:27:in `run'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor/base.rb:485:in `start'
  /Users/david/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/exe/tapioca:6:in `<top (required)>'
  /Users/david/.rbenv/versions/2.6.5/bin/tapioca:23:in `load'
  /Users/david/.rbenv/versions/2.6.5/bin/tapioca:23:in `<top (required)>'

Some `actionview` includes missing

This is part of the rbi that Tapioca generates for actionview 5.2.3:

module ActionView::Helpers
  include(::ActionView::Helpers::RenderingHelper)
  include(::ActionView::Helpers::RecordTagHelper)
  include(::ActionView::Helpers::NumberHelper)
  include(::ActionView::Helpers::JavaScriptHelper)
  include(::ActionView::Helpers::FormOptionsHelper)
  include(::ActionView::Helpers::TextHelper)
  include(::ActionView::Helpers::DebugHelper)
  include(::ActionView::Helpers::TagHelper)
  include(::ActionView::Helpers::OutputSafetyHelper)
  include(::ActionView::Helpers::DateHelper)
  include(::ActionView::Helpers::CsrfHelper)
  include(::ActionView::Helpers::CspHelper)
  include(::ActionView::Helpers::ControllerHelper)
  include(::ActionView::Helpers::CaptureHelper)
  include(::ActionView::Helpers::SanitizeHelper)
  include(::ActionView::Helpers::CacheHelper)
  include(::ActionView::Helpers::AtomFeedHelper)
  include(::ActionView::Helpers::AssetUrlHelper)
  include(::ActionView::Helpers::ActiveModelHelper)
  include(::ActiveSupport::Benchmarkable)
  extend(::ActiveSupport::Concern)
  extend(::ActiveSupport::Autoload)

  def self.eager_load!; end
end

This is different to the list of includes here: https://github.com/rails/rails/blob/v5.2.3/actionview/lib/action_view/helpers.rb

I'm missing TranslationHelper in particular, but there's many discrepancies.

I'm not sure if this is a Tapioca issue or something else; I dove into the code and it looks like you're depending on constant.ancestors. On a fresh Rails app, ActionView::Helpers.ancestors generates a similarly incomplete looking list.

$ bundle exec rails c
Running via Spring preloader in process 30823
Loading development environment (Rails 6.0.2.1)
2.6.3 :001 > pp ActionView::Helpers.ancestors
[ActionView::Helpers,
 ActionView::Helpers::RenderingHelper,
 ActionView::Helpers::NumberHelper,
 ActionView::Helpers::JavaScriptHelper,
 ActionView::Helpers::FormOptionsHelper,
 ActionView::Helpers::TextHelper,
 ActionView::Helpers::DebugHelper,
 ActionView::Helpers::TagHelper,
 ActionView::Helpers::OutputSafetyHelper,
 ActionView::Helpers::DateHelper,
 ActionView::Helpers::CsrfHelper,
 ActionView::Helpers::CspHelper,
 ActionView::Helpers::ControllerHelper,
 ActionView::Helpers::CaptureHelper,
 ActionView::Helpers::SanitizeHelper,
 ActionView::Helpers::CacheHelper,
 ActionView::Helpers::AtomFeedHelper,
 ActionView::Helpers::AssetUrlHelper,
 ActionView::Helpers::ActiveModelHelper,
 ActiveSupport::Benchmarkable]

Has anyone come across this before or got any tips on how to work around it?

Can't resolve e2mmap/thwait gems when generating on Ruby 2.6.5

This change in 0.2.8 causes an error when trying to run bundle exec tapioca generate on Ruby 2.6.5:

Errno::ENOENT: No such file or directory @ realpath_rec - /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thwait-0.1.0
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/gemfile.rb:92:in `realpath'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/gemfile.rb:92:in `initialize'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/gemfile.rb:36:in `new'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/gemfile.rb:36:in `block in dependencies'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bundler-2.1.4/lib/bundler/spec_set.rb:147:in `each'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bundler-2.1.4/lib/bundler/spec_set.rb:147:in `each'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/gemfile.rb:36:in `map'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/gemfile.rb:36:in `dependencies'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5460/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5460/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:254:in `gems_to_generate'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5460/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5460/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/generator.rb:58:in `build_gem_rbis'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5460/lib/types/private/methods/_methods.rb:240:in `call'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sorbet-runtime-0.5.5460/lib/types/private/methods/_methods.rb:240:in `block in _on_method_added'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/cli.rb:56:in `block in generate'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca.rb:10:in `silence_warnings'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/lib/tapioca/cli.rb:55:in `generate'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor/command.rb:27:in `run'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/thor-1.0.1/lib/thor/base.rb:485:in `start'
  /Users/me/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/tapioca-0.2.8/exe/tapioca:6:in `<top (required)>'
  /Users/me/.rbenv/versions/2.6.5/bin/tapioca:23:in `load'
  /Users/me/.rbenv/versions/2.6.5/bin/tapioca:23:in `<top (required)>'

You can replicate by adding e.g. gem 'sidekiq-scheduler' to your Gemfile, which depends on thwait and e2mmap.

These gems in Ruby 2.6.5, so they're not installed by bundler. The path that tapioca tries to load from, above, is wrong.

The gems are actually located under lib/ruby/2.6.0. In my case that's /Users/me/.rbenv/versions/2.6.5/lib/ruby/2.6.0/thwait

Version 0.2.7 works as expected.

Generated rbi files for some gems don't properly check with latest sorbet

Some rbi files that are generated by tapioca aren't properly type checked by Sorbet 0.5.5843.

Example for thor:

Gemfile:

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "sorbet"
gem "tapioca"
gem "thor"

Gemfile.lock:

GEM
  remote: https://rubygems.org/
  specs:
    coderay (1.1.3)
    method_source (1.0.0)
    pry (0.13.1)
      coderay (~> 1.1)
      method_source (~> 1.0)
    sorbet (0.5.5843)
      sorbet-static (= 0.5.5843)
    sorbet-runtime (0.5.5843)
    sorbet-static (0.5.5843-universal-darwin-14)
    tapioca (0.4.0)
      pry (>= 0.12.2)
      sorbet-runtime
      sorbet-static (>= 0.4.4471)
      thor (>= 0.19.2)
    thor (1.0.1)

PLATFORMS
  ruby

DEPENDENCIES
  sorbet
  tapioca
  thor

BUNDLED WITH
   2.1.2

How to reproduce:

$ bundle exec srb init
$ bundle exec tapioca gen thor
$ bundle exec srb tc

Expected: No errors.

Got:

sorbet/rbi/gems/[email protected]:31: circular argument reference _ https://srb.help/2001
    31 |  def self.package_name(name, _ = _); end

Looks like that Sorbet version doesn't like the _ = _ pattern for unused parameters. If you manually edit the rbi file an change this to _unused = _ it works.

DSL support for rails 4?

Hi, tapioca team.

I recently tried running tapioca dsl using it on my apps (rails v 4.2.7.1), but got error:

.../gems/tapioca-0.4.10/lib/tapioca/loader.rb:112:in eager_load_rails_app': undefined method `autoloaders` for Rails:Module (NoMethodError)

I then commented the line, but it fails again at

gems/activerecord-4.2.7.1/lib/active_record/dynamic_matchers.rb:26:in `method_missing': undefined method `generated_relation_methods' for #<Class:0x007fea82ab89d8> (NoMethodError)

It looks like rails 4 do not have generated_relation_methods.

Can you add support rails 4? Thanks!

Migration Guide / Moving from `srb rbi gems`

Hey everyone!

First off thanks so much for an awesome tool! It seems like a really great tool for the Sorbet ecosystem.

I'm trying to move some Rails apps over to Tapioca that were previously setup with srb rbi gems. Here was my approach for both these projects and the hiccups I ran into. If this is the correct migration guide for the most part I would be more than happy to help in committing it back to the docs!

Steps:

  • Get a clean start for gem RBI rm -rf sorbet/rbi/gems
  • Add tapioca to the Gemfile
  • tapioca init
  • Fix require file
  • tapioca sync
  • srb rbi hidden-definitions -- I am not sure about this step
  • tapioca todo

After this I am in a pretty decent spot. I have a few issues that look like real things Tapioca helped catch. But I do have a few gem related type issues, and some places where the sorbet-typed definitions conflict with the tapioca gened RBIs.
In an effort to resolve those conflicts I ran srb rbi sorbet-typed and pulled in the latest rbis from there, which led to MORE conflicts between the sorbet-typed types and the auto-generated ones.

Specific Questions:

  • Is Tapioca supposed to be used in conjunction with srb rbi hidden-definitions?
  • Is Tapioca supposed to be used in conjunction with srb rbi sorbet-typed?

I notice that BOTH of those are not included in this repos rbi files, but would love some guidance on the expected workflow here! I can't tell if they are not included here cause they are not needed in this use case or cause they aren't part of the intended workflow

Thanks again everyone!

Make `generate` mode overwrite RBI files

Currently tapioca does not overwrite files when generating RBI files for gems. However, for generate mode which works on a list of gem names, that is not the correct thing to do, since users can want to regenerate a gem's RBI file at the same version and the RBI file contents should be updated.

DSL GeneratedAssociationMethods always optional

belongs_to accepts an optional argument to specify whether the field is nilable or not. Much of my code assumes that belongs_to will NOT be nil, but the sorbet generated is specifying them to be nilable.

In fact in rails 5.1 optional: false became the default (rails/rails#34454) so this probably affects quite a few codebases.

PS. This project is fantastic, the idea of replacing quite a few srb commands (and rails_rbi:all) with faster commands from tapioca is very appealing.

I should note, I am happy to attempt work on a PR if that is desirable.

Initialize method not generated for non struct classes that use `const` and `prop`

Problem

We have a gem that uses sorbet, and another repo which requires this gem (and also uses Sorbet). In our gem we frequently use const and prop to define accessors (and constructors) for our classes. This works perfectly fine in the gem source, but users of the gem do not have an initialize method generated for these classes resulting in errors.

Reproduce

For some source like this:

module Maestro
  module Run
    class Configuration
      include Maestro::Base

      const :checkpoint_selector, CheckpointSelector, default: CheckpointSelector::None.new
      const :checkpoint_handler, CheckpointHandler, default: CheckpointHandler::NoOp.new
      const :checkpoint_lookup, CheckpointLookup, default: CheckpointLookup::ByExpressionInstanceId
      const :concurrency_mechanism, ConcurrencyMechanism
    end
  end
end

the generated rbi's look like this:

class Maestro::Run::Configuration
  include(::Maestro::Base)
  extend(::Maestro::Base::Signatures)

  def checkpoint_handler; end
  def checkpoint_lookup; end
  def checkpoint_selector; end
  def concurrency_mechanism; end
end

So we cannot do things such as this (from users of the gem), which should be legal:

Maestro::Run::Configuration.new(
  checkpoint_selector: Maestro::Run::CheckpointSelector::Recovery.new,
  checkpoint_handler: ArrayCheckpointHandler.new(@producing_queue, run),
  concurrency_mechanism: concurrency
)

due to the following error:

lib/maestro_service/worker.rb:161: Wrong number of arguments for constructor. Expected: 0, got: 1 https://srb.help/7004
     161 |        Maestro::Run::Configuration.new(
     162 |          checkpoint_selector: Maestro::Run::CheckpointSelector::Recovery.new,
     163 |          checkpoint_handler: ArrayCheckpointHandler.new(@producing_queue, run),
     164 |          concurrency_mechanism: concurrency
     165 |        )

Expected

Users of the gem should be able to construct classes that do not inherit from T::Struct but do use const and prop.

Workaround

There is a work around that involves defining a shim under sorbet/rbi/shims that provides a usable constructor. Something like this:

module Maestro
  module Run
    class Configuration
      sig do
        params(
          concurrency_mechanism: ConcurrencyMechanism,
          checkpoint_selector: CheckpointSelector,
          checkpoint_handler: CheckpointHandler,
          checkpoint_lookup: CheckpointLookup,
        ).void
      end
      def initialize(
        concurrency_mechanism:,
        checkpoint_selector: CheckpointSelector::None.new,
        checkpoint_handler: CheckpointHandler::NoOp.new,
        checkpoint_lookup: CheckpointLookup::ByExpressionInstanceId.new
      );
      end
    end
  end
end

Generating rbi files for current gem

tapioca works well for gem project dependencies - I am hoping to use it for a gem itself. There doesn't seem to be much in sorbet's docs about how to go about doing that, so I am hoping tapioca could step in.

eg, if I have my own gem called mygem, I would like to do:

cd mygem
bundle exec tapioca local_rbi # This command doesn't exist yet

It would require all my gem's dependencies and walk them to get a baseline, then walk the code in this gem and output the difference to the gem's rbi folder.

Is that how Shopify handle things internally, or is there a technique I have missed to do this?

I understand the answer may be "ask the sorbet team". People are asking similar questions like this one.

Thanks!

Trim trailing blank lines in generated files

Tapioca ends up creating trailing empty lines, especially when it outputs an empty RBI file. It would be desirable to trim those so that the output is neater and more predictable.

Doesn't work with `sorbet-rails`

You guys must be sick of me :)

Have a look at https://gist.github.com/ghiculescu/eed3ba99051ce053d18268e4beb75d07. The first file is what's in our codebase from running srb rbi gems ages ago. The second file is what tapioca is generating for sorbet-rails.

When running srb tc we get lots of errors like this:

sorbet/rails-rbi/pluck_to_tstruct.rbi:8: <todo sym> cannot be used for type member ::ITypeAssert::Elem https://srb.help/7028
     8 |      ta_struct: ITypeAssert[T.type_parameter(:U)],
                                     ^^^^^^^^^^^^^^^^^^^^
    sorbet/rails-rbi/pluck_to_tstruct.rbi:8: <todo sym> is not a subtype of <todo sym>
     8 |      ta_struct: ITypeAssert[T.type_parameter(:U)],
                                     ^^^^^^^^^^^^^^^^^^^^

sorbet/rails-rbi/parameters.rbi:8: <todo sym> cannot be used for type member ::ITypeAssert::Elem https://srb.help/7028
     8 |    params(key: Symbol, ta: ITypeAssert[T.type_parameter(:U)]).
                                                ^^^^^^^^^^^^^^^^^^^^
    sorbet/rails-rbi/parameters.rbi:8: <todo sym> is not a subtype of <todo sym>
     8 |    params(key: Symbol, ta: ITypeAssert[T.type_parameter(:U)]).
                                                ^^^^^^^^^^^^^^^^^^^^

I think https://gist.github.com/ghiculescu/eed3ba99051ce053d18268e4beb75d07#file-tapioca-L27 is the culprit. There isn't an equivalent line in the srb rbi gems version.


Note that sorbet-rails bundles its own rbis that also cover this https://github.com/chanzuckerberg/sorbet-rails/tree/master/lib/bundled_rbi - so skipping this in some way might be an option. It's not a good idea to skip the entire sorbet-rails gem though because then none of the generator code works.

Remove dynamic headers from generated RBIs

We dynamically add the generator command to the header of generated RBIs. Which will cause some headaches and false positives for tapioca dsl --verify. These should be static and either have a hardcoded command that should be used to resolve them, or point to documentation.

Association DSL generates generic CollectionProxy sig

If I have this model

class User < ApplicationRecord
  has_many :api_keys
end

When I run tapioca dsl, the user.rbi that is generated contains this:

module User::GeneratedAssociationMethods
  sig { returns(::ActiveRecord::Associations::CollectionProxy[ApiKey]) }
  def api_keys; end
end

When I run srb tc, Sorbet complains of a type error

$ bundle exec srb tc      
sorbet/rbi/dsl/user.rbi:18: Method [] does not exist on T.class_of(ActiveRecord::Associations::CollectionProxy) https://srb.help/7003
    18 |  sig { returns(::ActiveRecord::Associations::CollectionProxy[ApiKey]) }
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Did you mean:
    sorbet/rbi/gems/[email protected]:2989: ActiveRecord::Delegation#[]
    2989 |  def [](*_arg0, &_arg1); end
            ^^^^^^^^^^^^^^^^^^^^^^

I also checked out the sorbet/rbi/gems/[email protected] file that was generated when I ran tapioca sync, and in there CollectionProxy is only defined like

class ActiveRecord::Associations::CollectionProxy < ::ActiveRecord::Relation
  # snip
end

So I'm assuming the problem is that the type definition for CollectionProxy is not generic, but the generated user.rbi is assuming it is and trying to pass a type parameter.

This was starting from a code base with neither Sorbet nor Tapioca, running these commands:

$ bundle install
$ bundle exec tapioca init
$ bundle exec tapioca sync
$ bundle exec tapioca dsl

Did I miss some other step?

My version info:

$ bundle exec srb --version    
Sorbet typechecker 0.5.6130 git 8d32fbeb034f9f209e70223af008b5b47f181e6a debug_symbols=true clean=1
$ bundle exec tapioca --version
Tapioca v0.4.10

Missing Constant Tapioca

I was trying to update the type files for my gem runtime-client and was running into issues. A teammate and I managed to make a hack fix to the issue and thought that we should create an issue in case this wasn't previously reported.

Running dev typecheck update gave me this error:
Screen Shot 2019-07-02 at 4 12 44 PM

I made this change to the local version of my tapioca gem and after I did that it worked:
Screen Shot 2019-07-02 at 4 13 06 PM
Screen Shot 2019-07-02 at 4 13 13 PM

problems with net/ssh

I recently switched to using this, and it worked great for everything except net/ssh (strangely net/scp was fine). It doesn't seem to generate anything for net/ssh, and I have to manually add a few methods and classes for it.

I'm okay with keeping the manual rbi file with fixes for net/ssh, but figured I'd open this since it was the only thing that didn't work out.

Gemfile:

gem "bcrypt_pbkdf", require: false
gem "net-ssh", require: false
gem "ed25519", require: false

gem "sorbet"
gem "tapioca"

sorbet/tapioca/require.rb

# frozen_string_literal: true
# typed: false


# also have this as the only ruby file 'app.rb' in the project other than the gemfile and sorbet dir
require "net/ssh"
require "ed25519"
p Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader

tapioca generate

it finds the class that was p in require, so that's good

Requiring all gems to prepare for compiling... Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader
 Done

Processing 'ast' gem:
  Compiling ast, this may take a few seconds...   Done

Processing 'bcrypt_pbkdf' gem:
  Compiling bcrypt_pbkdf, this may take a few seconds...   Done

Processing 'coderay' gem:
  Compiling coderay, this may take a few seconds...   Done

Processing 'commander' gem:
  Compiling commander, this may take a few seconds...   Done

Processing 'ed25519' gem:
  Compiling ed25519, this may take a few seconds...   Done

Processing 'highline' gem:
  Compiling highline, this may take a few seconds...   Done

Processing 'method_source' gem:
  Compiling method_source, this may take a few seconds...   Done

Processing 'net-ssh' gem:
  Compiling net-ssh, this may take a few seconds...   Done

Processing 'parlour' gem:
  Compiling parlour, this may take a few seconds...   Done

Processing 'parser' gem:
  Compiling parser, this may take a few seconds...   Done

Processing 'pry' gem:
  Compiling pry, this may take a few seconds...   Done

Processing 'rainbow' gem:
  Compiling rainbow, this may take a few seconds...   Done

Processing 'thor' gem:
  Compiling thor, this may take a few seconds...   Done

All operations performed in working directory.
Please review changes and commit them.

sorbet/rbi/gems/[email protected]

it seems to have not found anything to add to this rib

# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `net-ssh` gem.
# Please instead update this file by running `tapioca generate`.

# typed: true

sorbet/rbi/gems/[email protected]

this one has things though

# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `ed25519` gem.
# Please instead update this file by running `tapioca generate`.

# typed: true

module Ed25519

  private

  def self_test; end
  def validate_key_bytes(key_bytes); end

  class << self
    def provider; end
    def provider=(_); end
    def self_test; end
    def validate_key_bytes(key_bytes); end
  end
end

Ed25519::KEY_SIZE = T.let(T.unsafe(nil), Integer)

module Ed25519::Provider
end

module Ed25519::Provider::Ref10
  class << self
    def create_keypair(_); end
    def sign(_, _); end
    def verify(_, _, _); end
  end
end

Ed25519::SIGNATURE_SIZE = T.let(T.unsafe(nil), Integer)

class Ed25519::SelfTestFailure < ::StandardError
end

class Ed25519::SigningKey
  def initialize(seed); end

  def inspect; end
  def keypair; end
  def seed; end
  def sign(message); end
  def to_bytes; end
  def to_str; end
  def verify_key; end

  class << self
    def from_keypair(keypair); end
    def generate; end
  end
end

Ed25519::VERSION = T.let(T.unsafe(nil), String)

class Ed25519::VerifyError < ::StandardError
end

class Ed25519::VerifyKey
  def initialize(key); end

  def inspect; end
  def to_bytes; end
  def to_str; end
  def verify(signature, message); end
end

srb tc

./app.rb:3: Unable to resolve constant ED25519 https://srb.help/5002
     3 |p Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Autocorrect: Use `-a` to autocorrect
    ./app.rb:3: Replace with Ed25519
     3 |p Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ./sorbet/rbi/gems/[email protected]:7: Did you mean: Ed25519?
     7 |module Ed25519
        ^^^^^^^^^^^^^^

./sorbet/tapioca/require.rb:8: Unable to resolve constant ED25519 https://srb.help/5002
     8 |p Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Autocorrect: Use `-a` to autocorrect
    ./sorbet/tapioca/require.rb:8: Replace with Ed25519
     8 |p Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ./sorbet/rbi/gems/[email protected]:7: Did you mean: Ed25519?
     7 |module Ed25519
        ^^^^^^^^^^^^^^
Errors: 2

Make errors from DSL generators more approachable

Currently, when an error is raised during the processing of a DSL generator, we fail with a printed stack trace and nothing else. That makes it really hard to debug the failure, since it is not even obvious which source file the generator is failing on.

We need to display better errors that include:

  • which generator caused an error
  • for which constant
  • with what error message and stack trace

Generating rbi for gem fails when `singleton_class` overridden

Attempting to generate rbi's for gems in Services DB using tapioca generate gems is failing with the following exception:

  Compiling rack-mini-profiler, this may take a few seconds... bundler: failed to load command: tapioca (/Users/mattvh/.gem/ruby/2.6.5/bin/tapioca)
NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
Did you mean?  singleton_class?
  /Users/mattvh/.gem/ruby/2.6.5/gems/tapioca-0.2.3/lib/tapioca/compilers/symbol_table/symbol_generator.rb:324:in `compile_methods'
  /Users/mattvh/.gem/ruby/2.6.5/gems/tapioca-0.2.3/lib/tapioca/compilers/symbol_table/symbol_generator.rb:164:in `block in compile_body'
  /Users/mattvh/.gem/ruby/2.6.5/gems/tapioca-0.2.3/lib/tapioca/compilers/symbol_table/symbol_generator.rb:437:in `with_indentation'
  /Users/mattvh/.gem/ruby/2.6.5/gems/tapioca-0.2.3/lib/tapioca/compilers/symbol_table/symbol_generator.rb:163:in `compile_body'

It looks like I can't call singleton_class on Rack::MiniProfiler because a private method with a different signature but the same name is being defined here: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/lib/mini_profiler/profiling_methods.rb#L147

[21] pry(main)> Rack::MiniProfiler.singleton_class
NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
Did you mean?  singleton_class?
from (pry):15:in `<main>'
[22] pry(main)> Rack::MiniProfiler.send(:singleton_class)
ArgumentError: wrong number of arguments (given 0, expected 1)
from /Users/mattvh/.gem/ruby/2.6.5/gems/rack-mini-profiler-1.1.1/lib/mini_profiler/profiling_methods.rb:147:in `singleton_class'
[23] pry(main)> Rack::MiniProfiler.method(:singleton_class).source_location
=> ["/Users/mattvh/.gem/ruby/2.6.5/gems/rack-mini-profiler-1.1.1/lib/mini_profiler/profiling_methods.rb", 147]

Tapioca::Loader relies on Rails 6+

Run Tapioca dsl on a Rails 5.2.4.4 app and you'll get this:

bash-4.4# bundle exec tapioca dsl
Loading Rails application... Creating scope :authorisable_content. Overwriting existing method Objective.authorisable_content.
# ...
bundler: failed to load command: tapioca (/usr/local/bundle/bin/tapioca)
NoMethodError: undefined method `autoloaders' for Rails:Module
Did you mean?  autoloads
               autoload
               autoload?
               autoload_at
  /usr/local/bundle/gems/tapioca-0.4.13/lib/tapioca/loader.rb:112:in `eager_load_rails_app'
# ...

Given your test suite uses Rails 5.2 I was struggling to work out how it wasn't causing issues for you, but it appears to be because its being monkey patched in:


As best I can tell Rails.autoloaders is a Zeitwerk thing, or at least a 6 thing
https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#rails-autoloaders

tapioca sync fails because of nokogiri native gem having different dependencies than compiled gem

Essentially, this error occurs when I run bundle exec tapioca sync on my Rails app vglist:

Connors-MacBook-Pro-2:vglist connorshea$ bundle exec tapioca sync
Removing RBI files of gems that have been removed:

bundler: failed to load command: tapioca (/Users/connorshea/.rbenv/versions/2.7.2/bin/tapioca)
Traceback (most recent call last):
	44: from /Users/connorshea/.rbenv/versions/2.7.2/bin/bundle:23:in `<main>'
	43: from /Users/connorshea/.rbenv/versions/2.7.2/bin/bundle:23:in `load'
	42: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/exe/bundle:37:in `<top (required)>'
	41: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/friendly_errors.rb:130:in `with_friendly_errors'
	40: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/exe/bundle:49:in `block in <top (required)>'
	39: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/cli.rb:24:in `start'
	38: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
	37: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/cli.rb:30:in `dispatch'
	36: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
	35: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
	34: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
	33: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/cli.rb:497:in `exec'
	32: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:28:in `run'
	31: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:63:in `kernel_load'
	30: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:63:in `load'
	29: from /Users/connorshea/.rbenv/versions/2.7.2/bin/tapioca:23:in `<top (required)>'
	28: from /Users/connorshea/.rbenv/versions/2.7.2/bin/tapioca:23:in `load'
	27: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/exe/tapioca:6:in `<top (required)>'
	26: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.0.1/lib/thor/base.rb:485:in `start'
	25: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch'
	24: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command'
	23: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.0.1/lib/thor/command.rb:27:in `run'
	22: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca/cli.rb:92:in `sync'
	21: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca.rb:10:in `silence_warnings'
	20: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/site_ruby/2.7.0/rubygems/user_interaction.rb:46:in `use_ui'
	19: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca.rb:11:in `block in silence_warnings'
	18: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca/cli.rb:93:in `block in sync'
	17: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `block in _on_method_added'
	16: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `call'
	15: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca/generator.rb:149:in `sync_rbis_with_gemfile'
	14: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `block in _on_method_added'
	13: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `call'
	12: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca/generator.rb:332:in `perform_removals'
	11: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `block in _on_method_added'
	10: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `call'
	 9: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca/generator.rb:298:in `removed_rbis'
	 8: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `block in _on_method_added'
	 7: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `call'
	 6: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca/generator.rb:270:in `expected_rbis'
	 5: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `block in _on_method_added'
	 4: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6189/lib/types/private/methods/_methods.rb:233:in `call'
	 3: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.13/lib/tapioca/gemfile.rb:35:in `dependencies'
	 2: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/spec_set.rb:81:in `materialize'
	 1: from /Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/spec_set.rb:81:in `map!'
/Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.3/lib/bundler/spec_set.rb:87:in `block in materialize': Could not find mini_portile2-2.5.0 in any of the sources (Bundler::GemNotFound)

I tried upgrading RubyGems, upgrading Bundler, upgrading Sorbet, downgrading Tapioca from 0.4.13 to 0.4.10, running bundle pristine to reinstall absolutely every gem in my Gemfile, and none of it changed anything. It occurs on both Bundler 2.2.3 and 2.2.5.

The only thing that fixed it was downgrading nokogiri from 1.11.1 to 1.10.10, which removes the native gem differentiation.

I suspect it has to do with this, nokogiri 'proper' requiring mini_portile2 but its native versions not?:

    nokogiri (1.11.1)
      mini_portile2 (~> 2.5.0)
      racc (~> 1.4)
    nokogiri (1.11.1-x86_64-darwin)
      racc (~> 1.4)
    nokogiri (1.11.1-x86_64-linux)
      racc (~> 1.4)

It looks like it's an intentional choice by Nokogiri, because mini_portile2 is required to compile nokogiri, but if you have a pre-compiled version there's no reason to install it: sparklemotion/nokogiri#2078

So that'd explain why we don't have the gem locally on macOS. I suspect we need to figure out a way to filter out gems that wouldn't exist locally?

Declaring a class method called "hash" leads to an unhelpful error

We had a class declaration like this:

class FooTest
  class << self
    def hash
      @hash ||= {}
    end
  end
end

When we ran Tapioca on it, we saw this:

  Compiling foo_project, this may take a few seconds... bundler: failed to load command: tapioca (2.6.6/bin/tapioca)
TypeError: no implicit conversion of Hash into Integer
  ruby/2.6.0/set.rb:254:in `include?'

We found it comes from here:

constant.ancestors.reject { |mod| ignorable_ancestors.include?(mod) }

It is true that Set.new.include?(FooTest) blows up like that.

I was going to create a PR, but thought I'd check how you'd like to handle it first. Thanks!

Attribute accessors defined in concerns are not generated

When declaring attribute accessors within an included block of an ActiveSupport::Concern, the resulting methods are not generated in the signatures. For example,

# The concern
module Taggable
  extend ActiveSupport::Concern

  included do
    attr_accessor :tag
  end
end

# A class including the concern
class Article
  include Taggable
end

# Methods exist in `Article`, but are not generated in the RBIs
article = Article.new
article.tag = "my tag"
article.tag # => "my tag"

# Expected RBI
class Article
  def tag=(tag); end
  
  def tag; end
end

[Feature request] Support ORM Sequel

Hi! tapioca. Thank you for creating an amazing gem, it helps me resolve many problems which I have to deal manually with sorbet gem.

We are using ActiveRecord and Sequel gems, both of them inside our codebases. After I tried using Tapioca for Sequel project, just realized that we didn't have any dsl compiler for Sequel, only ActiveRecord at the moment.

Is there any chance that I can request a DSL compiler for Sequel? please let me know if I can contribute to this project too. I'm willing to do it, and again thanks for this great gem.

`bundle exec tapioca dsl` failing due to polymorphic association

I'm trying to add sorbet to an existing app, and running into a weird issue when I run bundle exec tapoica dsl. It seems to be failing due to polymorphic associations, although I'm not totally sure if thats the root cause.

The error message is kind of confusing though, because I dont even really know what file is causing it, so I'm not sure where to start looking for problems. It generates a bunch of rbis, and the throws this exception (actual project name has been replaced by my-project). We are using rails 6-1-stable:

bundler: failed to load command: tapioca (/nix/store/b562r5qm44p6yzashqaxf1lbspg9zfh9-gem-bundle-my-project/lib/ruby/gems/2.7.0/bin/tapioca)
Traceback (most recent call last):
	53: from /nix/store/925idj4lmy5njx1bdar42yilgscniivv-shadowenv-ruby-env-my-project/bin/bundle:47:in `<main>'
	52: from /nix/store/925idj4lmy5njx1bdar42yilgscniivv-shadowenv-ruby-env-my-project/bin/bundle:47:in `load'
	51: from /nix/store/b562r5qm44p6yzashqaxf1lbspg9zfh9-gem-bundle-my-project/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/exe/bundle:37:in `<top (required)>'
	50: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/friendly_errors.rb:130:in `with_friendly_errors'
	49: from /nix/store/b562r5qm44p6yzashqaxf1lbspg9zfh9-gem-bundle-my-project/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/exe/bundle:49:in `block in <top (required)>'
	48: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:24:in `start'
	47: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
	46: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:30:in `dispatch'
	45: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
	44: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
	43: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
	42: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli.rb:494:in `exec'
	41: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:28:in `run'
	40: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:63:in `kernel_load'
	39: from /nix/store/ardx7f1kq44r2yd53f8f38sia8j0pmr9-bundler-2.2.5/lib/ruby/gems/2.7.0/gems/bundler-2.2.5/lib/bundler/cli/exec.rb:63:in `load'
	38: from /nix/store/b562r5qm44p6yzashqaxf1lbspg9zfh9-gem-bundle-my-project/lib/ruby/gems/2.7.0/bin/tapioca:23:in `<top (required)>'
	37: from /nix/store/b562r5qm44p6yzashqaxf1lbspg9zfh9-gem-bundle-my-project/lib/ruby/gems/2.7.0/bin/tapioca:23:in `load'
	36: from /nix/store/b562r5qm44p6yzashqaxf1lbspg9zfh9-gem-bundle-my-project/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/exe/tapioca:6:in `<top (required)>'
	35: from /nix/store/vwi3mpm7m6r8rw5nshmql6944k5xzj7k-ruby2.7.2-thor-1.1.0/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/base.rb:485:in `start'
	34: from /nix/store/vwi3mpm7m6r8rw5nshmql6944k5xzj7k-ruby2.7.2-thor-1.1.0/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
	33: from /nix/store/vwi3mpm7m6r8rw5nshmql6944k5xzj7k-ruby2.7.2-thor-1.1.0/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
	32: from /nix/store/vwi3mpm7m6r8rw5nshmql6944k5xzj7k-ruby2.7.2-thor-1.1.0/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
	31: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/cli.rb:78:in `dsl'
	30: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca.rb:10:in `silence_warnings'
	29: from /nix/store/rmn2fcvd2kxd42jh0zha9i7ygq7niphy-ruby-2.7.2/lib/ruby/2.7.0/rubygems/user_interaction.rb:47:in `use_ui'
	28: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca.rb:11:in `block in silence_warnings'
	27: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/cli.rb:79:in `block in dsl'
	26: from /nix/store/dpjfp2qv0bmnxd105y8b6zqi8wx3y56d-ruby2.7.2-sorbet-runtime-0.5.6292/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6292/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
	25: from /nix/store/dpjfp2qv0bmnxd105y8b6zqi8wx3y56d-ruby2.7.2-sorbet-runtime-0.5.6292/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6292/lib/types/private/methods/_methods.rb:224:in `call'
	24: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:135:in `build_dsl'
	23: from /nix/store/dpjfp2qv0bmnxd105y8b6zqi8wx3y56d-ruby2.7.2-sorbet-runtime-0.5.6292/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6292/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
	22: from /nix/store/dpjfp2qv0bmnxd105y8b6zqi8wx3y56d-ruby2.7.2-sorbet-runtime-0.5.6292/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6292/lib/types/private/methods/_methods.rb:224:in `call'
	21: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl_compiler.rb:47:in `run'
	20: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl_compiler.rb:47:in `each'
	19: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl_compiler.rb:48:in `block in run'
	18: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl_compiler.rb:87:in `rbi_for_constant'
	17: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl_compiler.rb:87:in `each'
	16: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl_compiler.rb:89:in `block in rbi_for_constant'
	15: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:113:in `decorate'
	14: from /nix/store/hym3fn64px6mrrbkqbj0pv2k88vy29f2-ruby2.7.2-parlour-5.0.0/lib/ruby/gems/2.7.0/gems/parlour-5.0.0/lib/parlour/rbi_generator/namespace.rb:134:in `path'
	13: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:116:in `block in decorate'
	12: from /nix/store/hym3fn64px6mrrbkqbj0pv2k88vy29f2-ruby2.7.2-parlour-5.0.0/lib/ruby/gems/2.7.0/gems/parlour-5.0.0/lib/parlour/rbi_generator/namespace.rb:283:in `create_module'
	11: from /nix/store/hym3fn64px6mrrbkqbj0pv2k88vy29f2-ruby2.7.2-parlour-5.0.0/lib/ruby/gems/2.7.0/gems/parlour-5.0.0/lib/parlour/rbi_generator/namespace.rb:283:in `new'
	10: from /nix/store/hym3fn64px6mrrbkqbj0pv2k88vy29f2-ruby2.7.2-parlour-5.0.0/lib/ruby/gems/2.7.0/gems/parlour-5.0.0/lib/parlour/rbi_generator/module_namespace.rb:32:in `initialize'
	 9: from /nix/store/hym3fn64px6mrrbkqbj0pv2k88vy29f2-ruby2.7.2-parlour-5.0.0/lib/ruby/gems/2.7.0/gems/parlour-5.0.0/lib/parlour/rbi_generator/namespace.rb:50:in `initialize'
	 8: from /nix/store/hym3fn64px6mrrbkqbj0pv2k88vy29f2-ruby2.7.2-parlour-5.0.0/lib/ruby/gems/2.7.0/gems/parlour-5.0.0/lib/parlour/rbi_generator/namespace.rb:50:in `yield_self'
	 7: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:118:in `block (2 levels) in decorate'
	 6: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:148:in `populate_associations'
	 5: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:148:in `each'
	 4: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:150:in `block in populate_associations'
	 3: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:227:in `populate_collection_assoc_getter_setter'
	 2: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:265:in `type_for'
	 1: from /nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:291:in `polymorphic_association?'
/nix/store/7jbqxb62ls9yl6ngqdadzwmrzm71p60y-ruby2.7.2-tapioca-0.4.14/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/compilers/dsl/active_record_associations.rb:290:in `polymorphic_association?': undefined method `through_reflection?' for nil:NilClass (NoMethodError)

Declaring monkey patch methods from a gem

I have an internal gem fooer that does this:

class Foo
  def foome
  end
end

Hash.class_exec do
  def fooit
  end
end

When that gem is used by another project, and I run tapioca on that project, the rbi it produces for the fooer gem does include Foo#foome but not Hash#fooit. Running srb produces rbi that does include it.

I can believe how deeply complex this can all get. Is there a brief summary of how tapioca decides which constants and methods it "finds"? I am going to guess it is basically "all constants that are created in the code examined, and all of their methods". If so, i can see why the above would not show up.

If that is right (ish) would you accept a PR for the README that explicitly describes what to do about missed (monkey patch) methods?

srb tc error: Bad parameter ordering

Hi,

with the following gemfile:

gem "sorbet"
gem "tapioca", "0.4.4"
gem "spoom", "1.0.4"

it installs these other deps, (but I don’t think this is relevant):

``` Using ast 2.4.1 Using bundler 2.1.4 Using sorbet-runtime 0.5.5881 Using coderay 1.1.3 Using highline 2.0.3 Using method_source 1.0.0 Using rainbow 3.0.0 Using colorize 0.8.1 Using sorbet-static 0.5.5881 (universal-darwin-19) Using thor 1.0.1 Using sorbet 0.5.5881 Using pry 0.13.1 Using parser 2.7.1.4 Using commander 4.5.2 Using parlour 4.0.1 Using spoom 1.0.4 Using tapioca 0.4.4 ```

It appears that the generated rbi for "spoom" is not valid: (I'm not sure what this gem is, just some transitive dependency for me)

bundle exec srb tc .
sorbet/rbi/gems/spoom@1.0.4.rbi:275: Bad parameter ordering for arg, expected path instead https://srb.help/5049
     275 |    def srb(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                       ^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:274: Expected index in signature:
     274 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns([String, T::Boolean]) }
                           ^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:275: Bad parameter ordering for path, expected capture_err instead https://srb.help/5049
     275 |    def srb(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                            ^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:274: Expected index in signature:
     274 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns([String, T::Boolean]) }
                                         ^^^^^^^^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:275: Bad parameter ordering for capture_err, expected arg instead https://srb.help/5049
     275 |    def srb(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                                                 ^^^^^^^^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:274: Expected index in signature:
     274 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns([String, T::Boolean]) }
                                                                  ^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:279: Bad parameter ordering for arg, expected path instead https://srb.help/5049
     279 |    def srb_metrics(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                               ^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:278: Expected index in signature:
     278 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns(T.nilable(Spoom::Sorbet::Metrics)) }
                           ^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:279: Bad parameter ordering for path, expected capture_err instead https://srb.help/5049
     279 |    def srb_metrics(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                                    ^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:278: Expected index in signature:
     278 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns(T.nilable(Spoom::Sorbet::Metrics)) }
                                         ^^^^^^^^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:279: Bad parameter ordering for capture_err, expected arg instead https://srb.help/5049
     279 |    def srb_metrics(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                                                         ^^^^^^^^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:278: Expected index in signature:
     278 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns(T.nilable(Spoom::Sorbet::Metrics)) }
                                                                  ^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:281: Bad parameter ordering for arg, expected path instead https://srb.help/5049
     281 |    def srb_tc(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                          ^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:280: Expected index in signature:
     280 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns([String, T::Boolean]) }
                           ^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:281: Bad parameter ordering for path, expected capture_err instead https://srb.help/5049
     281 |    def srb_tc(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                               ^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:280: Expected index in signature:
     280 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns([String, T::Boolean]) }
                                         ^^^^^^^^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:281: Bad parameter ordering for capture_err, expected arg instead https://srb.help/5049
     281 |    def srb_tc(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                                                    ^^^^^^^^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:280: Expected index in signature:
     280 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns([String, T::Boolean]) }
                                                                  ^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:283: Bad parameter ordering for arg, expected path instead https://srb.help/5049
     283 |    def srb_version(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                               ^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:282: Expected index in signature:
     282 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns(T.nilable(String)) }
                           ^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:283: Bad parameter ordering for path, expected capture_err instead https://srb.help/5049
     283 |    def srb_version(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                                    ^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:282: Expected index in signature:
     282 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns(T.nilable(String)) }
                                         ^^^^^^^^^^^

sorbet/rbi/gems/spoom@1.0.4.rbi:283: Bad parameter ordering for capture_err, expected arg instead https://srb.help/5049
     283 |    def srb_version(*arg, path: T.unsafe(nil), capture_err: T.unsafe(nil)); end
                                                         ^^^^^^^^^^^^
    sorbet/rbi/gems/spoom@1.0.4.rbi:282: Expected index in signature:
     282 |    sig { params(path: String, capture_err: T::Boolean, arg: String).returns(T.nilable(String)) }
                                                                  ^^^
Errors: 12

I can work around this by using my old rbi for spoom, since I don’t use it directly so it shouldn't matter much, but I figured it'd be good to report this.

net/ssh conflicting definitions between tapioca rbi and built in sorbet rbi

With a gemfile

 gem "sorbet"
 gem "tapioca"
 gem "net-ssh"

that is all latest resolved as of right now

Using ast 2.4.1
Using bundler 2.1.4
Using coderay 1.1.3
Using colorize 0.8.1
Using highline 2.0.3
Using commander 4.5.2
Using method_source 1.0.0
Using net-ssh 6.1.0
Using parser 2.7.1.5
Using rainbow 3.0.0
Using sorbet-runtime 0.5.5933
Using parlour 4.0.1
Using pry 0.13.1
Using sorbet-static 0.5.5933 (universal-darwin-19)
Using sorbet 0.5.5933
Using thor 1.0.1
Using spoom 1.0.4
Using tapioca 0.4.6

and a tapioca require file with just require "net/ssh", and nothing else in the project, srb tc fails

sorbet/rbi/gems/net-[email protected].rbi:167: Method Net::SSH::KnownHosts#keys_for redefined without matching argument count. Expected: 1, got: 2 https://srb.help/4010
     167 |  def keys_for(host, options = T.unsafe(nil)); end
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    https://github.com/sorbet/sorbet/tree/04ef993fe0d075126c2553989a3e1a91f508bd79/rbi/stdlib/net.rbi#L8459: Previous definition
    8459 |  def keys_for(host); end
            ^^^^^^^^^^^^^^^^^^

sorbet/rbi/gems/net-[email protected].rbi:176: Method Net::SSH::KnownHosts.search_in redefined without matching argument count. Expected: 2, got: 3 https://srb.help/4010
     176 |    def search_in(files, host, options = T.unsafe(nil)); end
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    https://github.com/sorbet/sorbet/tree/04ef993fe0d075126c2553989a3e1a91f508bd79/rbi/stdlib/net.rbi#L8471: Previous definition
    8471 |  def self.search_in(files, host); end
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Errors: 2

Incompatible with optional Bundler groups?

Hi,

I'm trying Tapioca for the first time. My project's Gemfile has some groups marked as optional: true, which seems to cause an issue when running tapioca sync:

Requiring all gems to prepare for compiling... /Users/awaite/.rvm/rubies/ruby-2.4.4/lib/ruby/site_ruby/2.4.0/bundler/spec_set.rb:43:in
`block in for': Unable to find a spec satisfying spring-watcher-listen in the set. Perhaps the lockfile is corrupted? (Bundler::GemNotFound)

I've worked around it by temporarily commenting out those groups.

I can look into opening a PR to fix this, just wanted to log the issue first.

tapioca (0.2.5)
Bundler version 1.17.3
ruby 2.4.4p296 (2018-03-28 revision 63013) [x86_64-darwin18]

sync: LoadError: cannot load such file -- RMagick

Trying to install tapioca for the first time, sync was unable to compile carrierwave.

My app does not use RMagick. RMagick is not a runtime dependency of CarrierWave.

bundle exec tapioca -v
Tapioca v0.4.14
bundle exec tapioca sync
...
  Compiling carrierwave, this may take a few seconds... bundler: failed to load command: tapioca (/Users/jared/.rbenv/versions/2.6.6/bin/tapioca)
LoadError: cannot load such file -- RMagick
..../ruby/gems/2.6.0/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:332:in `require'
..../ruby/gems/2.6.0/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:332:in `block in require'
..../ruby/gems/2.6.0/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:299:in `load_dependency'
..../ruby/gems/2.6.0/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:332:in `require'
..../ruby/gems/2.6.0/gems/carrierwave-2.1.1/lib/carrierwave/processing/rmagick.rb:65:in `rescue in block in <module:RMagick>'
..../ruby/gems/2.6.0/gems/carrierwave-2.1.1/lib/carrierwave/processing/rmagick.rb:62:in `block in <module:RMagick>'
..../ruby/gems/2.6.0/gems/activesupport-6.1.3/lib/active_support/concern.rb:136:in `class_eval'
..../ruby/gems/2.6.0/gems/activesupport-6.1.3/lib/active_support/concern.rb:136:in `append_features'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:349:in `include'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:349:in `block (2 levels) in compile_mixes_in_class_methods'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:359:in `compile_mixes_in_class_methods'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:184:in `block in compile_body'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:634:in `with_indentation'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:176:in `compile_body'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:162:in `compile_module'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:114:in `compile_constant'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:100:in `compile'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:231:in `block in compile_subconstants'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:222:in `map'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:222:in `compile_subconstants'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:170:in `compile_module'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:114:in `compile_constant'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:100:in `compile'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:74:in `generate_from_symbol'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:32:in `block in generate'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:32:in `map'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table/symbol_generator.rb:32:in `generate'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/compilers/symbol_table_compiler.rb:19:in `compile'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:434:in `compile_gem_rbi'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:380:in `block (2 levels) in perform_additions'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:371:in `each'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:371:in `block in perform_additions'
..../ruby/gems/2.6.0/gems/thor-1.1.0/lib/thor/shell/basic.rb:44:in `indent'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:365:in `perform_additions'
..../ruby/gems/2.6.0/gems/sorbet-runtime-0.5.6231/lib/types/private/methods/_methods.rb:233:in `call'
..../ruby/gems/2.6.0/gems/sorbet-runtime-0.5.6231/lib/types/private/methods/_methods.rb:233:in `block in _on_method_added'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:150:in `sync_rbis_with_gemfile'
..../ruby/gems/2.6.0/gems/sorbet-runtime-0.5.6231/lib/types/private/methods/_methods.rb:233:in `call'
..../ruby/gems/2.6.0/gems/sorbet-runtime-0.5.6231/lib/types/private/methods/_methods.rb:233:in `block in _on_method_added'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/cli.rb:93:in `block in sync'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca.rb:11:in `block in silence_warnings'
..../ruby/2.6.0/rubygems/user_interaction.rb:47:in `use_ui'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca.rb:10:in `silence_warnings'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/lib/tapioca/cli.rb:92:in `sync'
..../ruby/gems/2.6.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
..../ruby/gems/2.6.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
..../ruby/gems/2.6.0/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
..../ruby/gems/2.6.0/gems/thor-1.1.0/lib/thor/base.rb:485:in `start'
..../ruby/gems/2.6.0/gems/tapioca-0.4.14/exe/tapioca:6:in `<top (required)>'
..../bin/tapioca:23:in `load'
..../bin/tapioca:23:in `<top (required)>'

[Feature request] flag for generating only top-level gem rbi

I was looking into -x to exclude some transitive dependencies I don’t care about having the RBI for. While that would work, it got me thinking that what (I think?) I really want is just to have rbi generated for the gems I've explicitly added to my Gemfile, and none of the transitive gems that I don’t use directly.

Do you think such a flag would be a good idea?

Tapioca rendering incomplete RBI for httpclient gem

When running tapioca sync or generate on a project that includes the httpclient gem, the resulting RBI file has a syntax error:

...

WebAgent::CookieManager = 

I'll include the entire generated RBI in an expandable block at the end.

I've tracked the problem down to this call to resolve constant where it seems that we're failing to find HTTPClient::CookieManager. Thus, I think the resulting RBI should look like this:

WebAgent::CookieManager = HTTPClient::CookieManager
Full RBI
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `httpclient` gem.
# Please instead update this file by running `tapioca sync`.

# typed: true

module HTTP
  extend(::HTTP::Chainable)

  class << self
    def [](headers); end
  end
end

HTTP::CHARSET_RE = T.let(T.unsafe(nil), Regexp)

class HTTP::ContentType < ::Struct
  def charset; end
  def charset=(_); end
  def mime_type; end
  def mime_type=(_); end

  class << self
    def [](*_arg0); end
    def inspect; end
    def members; end
    def new(*_arg0); end
    def parse(str); end

    private

    def charset(str); end
    def mime_type(str); end
  end
end

HTTP::MIME_TYPE_RE = T.let(T.unsafe(nil), Regexp)

class HTTP::Message
  def initialize; end

  def body; end
  def body=(body); end
  def body_encoding; end
  def code; end
  def content; end
  def content_type; end
  def content_type=(content_type); end
  def contenttype; end
  def contenttype=(content_type); end
  def cookies; end
  def dump(dev = T.unsafe(nil)); end
  def header; end
  def headers; end
  def http_body; end
  def http_body=(body); end
  def http_header; end
  def http_header=(_arg0); end
  def http_version; end
  def http_version=(http_version); end
  def ok?; end
  def peer_cert; end
  def peer_cert=(_arg0); end
  def previous; end
  def previous=(_arg0); end
  def reason; end
  def reason=(reason); end
  def redirect?; end
  def see_other?; end
  def status; end
  def status=(status); end
  def status_code; end
  def version; end
  def version=(version); end

  class << self
    def create_query_part_str(query); end
    def escape(str); end
    def escape_query(query); end
    def file?(obj); end
    def get_mime_type_func; end
    def internal_mime_type(path); end
    def keep_alive_enabled?(version); end
    def mime_type(path); end
    def mime_type_handler; end
    def mime_type_handler=(handler); end
    def multiparam_query?(query); end
    def new_connect_request(uri); end
    def new_request(method, uri, query = T.unsafe(nil), body = T.unsafe(nil), boundary = T.unsafe(nil)); end
    def new_response(body, req = T.unsafe(nil)); end
    def parse(query); end
    def set_mime_type_func(handler); end
    def unescape(string); end
  end
end

class HTTP::Message::Body
  def initialize; end

  def chunk_size; end
  def chunk_size=(_arg0); end
  def content; end
  def dump(header = T.unsafe(nil), dev = T.unsafe(nil)); end
  def dump_chunked(header = T.unsafe(nil), dev = T.unsafe(nil)); end
  def init_request(body = T.unsafe(nil), boundary = T.unsafe(nil)); end
  def init_response(body = T.unsafe(nil)); end
  def positions; end
  def positions=(_arg0); end
  def size; end

  private

  def build_query_multipart_str(query, boundary); end
  def dump_chunk(str); end
  def dump_chunk_size(size); end
  def dump_chunks(io, dev); end
  def dump_file(io, dev, sz); end
  def dump_last_chunk; end
  def params_from_file(value); end
  def remember_pos(io); end
  def reset_pos(io); end
  def set_content(body, boundary = T.unsafe(nil)); end
end

HTTP::Message::Body::DEFAULT_CHUNK_SIZE = T.let(T.unsafe(nil), Integer)

class HTTP::Message::Body::Parts
  def initialize; end

  def add(part); end
  def parts; end
  def size; end
  def sizes; end

  private

  def add_size(part, sz); end
end

HTTP::Message::CRLF = T.let(T.unsafe(nil), String)

class HTTP::Message::Headers
  def initialize; end

  def [](key); end
  def []=(key, value); end
  def add(key, value); end
  def all; end
  def body_charset; end
  def body_charset=(_arg0); end
  def body_date; end
  def body_date=(_arg0); end
  def body_encoding; end
  def body_size; end
  def body_size=(body_size); end
  def body_type; end
  def body_type=(_arg0); end
  def chunked; end
  def chunked=(_arg0); end
  def content_type; end
  def content_type=(content_type); end
  def contenttype; end
  def contenttype=(content_type); end
  def create_query_part; end
  def create_query_uri; end
  def delete(key); end
  def dump; end
  def get(key = T.unsafe(nil)); end
  def http_version; end
  def http_version=(_arg0); end
  def init_connect_request(uri); end
  def init_request(method, uri, query = T.unsafe(nil)); end
  def init_response(status_code, req = T.unsafe(nil)); end
  def reason_phrase; end
  def reason_phrase=(_arg0); end
  def request_absolute_uri; end
  def request_absolute_uri=(_arg0); end
  def request_method; end
  def request_query; end
  def request_query=(_arg0); end
  def request_uri; end
  def request_uri=(_arg0); end
  def set(key, value); end
  def set_body_encoding; end
  def set_date_header; end
  def set_headers(headers); end
  def status_code; end
  def status_code=(status_code); end

  private

  def charset_label; end
  def request_line; end
  def response_status_line; end
  def set_header; end
  def set_request_header; end
  def set_response_header; end
end

HTTP::Message::Headers::CHARSET_MAP = T.let(T.unsafe(nil), Hash)

HTTP::Message::Headers::NIL_URI = T.let(T.unsafe(nil), T.untyped)

HTTP::Message::Headers::STATUS_CODE_MAP = T.let(T.unsafe(nil), Hash)

HTTP::Message::VERSION_WARNING = T.let(T.unsafe(nil), String)

module HTTP::Status
  class << self
    def redirect?(status); end
    def successful?(status); end
  end
end

HTTP::Status::ACCEPTED = T.let(T.unsafe(nil), Integer)

HTTP::Status::BAD_REQUEST = T.let(T.unsafe(nil), Integer)

HTTP::Status::CREATED = T.let(T.unsafe(nil), Integer)

HTTP::Status::FOUND = T.let(T.unsafe(nil), Integer)

HTTP::Status::INTERNAL = T.let(T.unsafe(nil), Integer)

HTTP::Status::MOVED_PERMANENTLY = T.let(T.unsafe(nil), Integer)

HTTP::Status::MOVED_TEMPORARILY = T.let(T.unsafe(nil), Integer)

HTTP::Status::NON_AUTHORITATIVE_INFORMATION = T.let(T.unsafe(nil), Integer)

HTTP::Status::NO_CONTENT = T.let(T.unsafe(nil), Integer)

HTTP::Status::OK = T.let(T.unsafe(nil), Integer)

HTTP::Status::PARTIAL_CONTENT = T.let(T.unsafe(nil), Integer)

HTTP::Status::PROXY_AUTHENTICATE_REQUIRED = T.let(T.unsafe(nil), Integer)

HTTP::Status::REDIRECT_STATUS = T.let(T.unsafe(nil), Array)

HTTP::Status::RESET_CONTENT = T.let(T.unsafe(nil), Integer)

HTTP::Status::SEE_OTHER = T.let(T.unsafe(nil), Integer)

HTTP::Status::SUCCESSFUL_STATUS = T.let(T.unsafe(nil), Array)

HTTP::Status::TEMPORARY_REDIRECT = T.let(T.unsafe(nil), Integer)

HTTP::Status::UNAUTHORIZED = T.let(T.unsafe(nil), Integer)

HTTP::VERSION = T.let(T.unsafe(nil), String)

HTTPClient = WebMockHTTPClient

JSONClient = WebMockJSONClient

class OpenSSL::X509::Store
  def initialize(*args); end

  def _httpclient_cert_store_items; end
  def add_cert(cert); end
  def add_file(cert); end
  def add_path(cert); end
end

class WebAgent
end

class WebAgent::Cookie < ::HTTP::Cookie
  def domain; end
  def flag; end
  def http_only?; end
  def original_domain; end
  def url; end
  def url=(url); end

  private

  def deprecated(old, new); end
end

WebAgent::CookieManager = 

undefined method `identifier' for #<Gem::Dependency:0x00007fc960079208> (NoMethodError)

I'm trying to run tapioca on my project and getting:

✔  bundle exec tapioca sync                                                                                                 99% (5:39) 🔋  3.00 L  RSpec: 60.00%
Removing RBI files of gems that have been removed:

bundler: failed to load command: tapioca (/Users/aeldaly/.rbenv/versions/2.7.2/bin/tapioca)
Traceback (most recent call last):
        54: from /Users/aeldaly/.rbenv/versions/2.7.2/bin/bundle:23:in `<main>'
        53: from /Users/aeldaly/.rbenv/versions/2.7.2/bin/bundle:23:in `load'
        52: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/exe/bundle:37:in `<top (required)>'
        51: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/friendly_errors.rb:130:in `with_friendly_errors'
        50: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/exe/bundle:49:in `block in <top (required)>'
        49: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/cli.rb:24:in `start'
        48: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
        47: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/cli.rb:30:in `dispatch'
        46: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
        45: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
        44: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
        43: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/cli.rb:494:in `exec'
        42: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/cli/exec.rb:28:in `run'
        41: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/cli/exec.rb:63:in `kernel_load'
        40: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/cli/exec.rb:63:in `load'
        39: from /Users/aeldaly/.rbenv/versions/2.7.2/bin/tapioca:23:in `<top (required)>'
        38: from /Users/aeldaly/.rbenv/versions/2.7.2/bin/tapioca:23:in `load'
        37: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/exe/tapioca:6:in `<top (required)>'
        36: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/base.rb:485:in `start'
        35: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
        34: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
        33: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
        32: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/cli.rb:92:in `sync'
        31: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca.rb:10:in `silence_warnings'
        30: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/2.7.0/rubygems/user_interaction.rb:47:in `use_ui'
        29: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca.rb:11:in `block in silence_warnings'
        28: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/cli.rb:93:in `block in sync'
        27: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
        26: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `call'
        25: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:149:in `sync_rbis_with_gemfile'
        24: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
        23: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `call'
        22: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:336:in `perform_removals'
        21: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
        20: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `call'
        19: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:302:in `removed_rbis'
        18: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
        17: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `call'
        16: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:274:in `expected_rbis'
        15: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
        14: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `call'
        13: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:172:in `bundle'
        12: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/generator.rb:172:in `new'
        11: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
        10: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `call'
         9: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/gemfile.rb:34:in `initialize'
         8: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `block in _on_method_added'
         7: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sorbet-runtime-0.5.6289/lib/types/private/methods/_methods.rb:224:in `call'
         6: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/tapioca-0.4.14/lib/tapioca/gemfile.rb:62:in `load_dependencies'
         5: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/spec_set.rb:80:in `materialize'
         4: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/spec_set.rb:20:in `for'
         3: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/spec_set.rb:20:in `loop'
         2: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/spec_set.rb:22:in `block in for'
         1: from /Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/spec_set.rb:22:in `include?'
/Users/aeldaly/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.2.9/lib/bundler/lazy_specification.rb:30:in `==': undefined method `identifier' for #<Gem::Dependency:0x00007fc960079208> (NoMethodError)
Did you mean?  identity
zsh: exit 1     bundle exec tapioca sync

Here is my gemfile

# frozen_string_literal: true

source 'https://rubygems.org'

gem 'karafka'
gem 'pg'
gem 'sequel'

gem 'sorbet-runtime'

group :development do
  gem 'rubocop'
  gem 'rubocop-rake'
  gem 'rubocop-rspec'
  gem 'rubocop-sequel'
  gem 'solargraph'
  gem 'sorbet'
  gem 'sorbet-progress'
  gem 'tapioca', require: false
end

group :development, :test do
  gem 'rspec'
end

group :test do
  gem 'database_cleaner-sequel'
  gem 'karafka-testing'
end

I also tried it by commenting out sorbet-runtime, sorbet, and sorbet-progress and running bundle install again.

I also tried following the instructions in #114

But they all fail with the same error.

Not sure how to proceed from here.

Thanks.

Failure to run on Ruby 2.3.7 due to sorbet-runtime

After including Tapioca into the Gemfile and bundle install, trying to run it - fails:

00:58 $ bundle exec tapioca
/Users/thunder/.rvm/gems/ruby-2.3.7gems/sorbet-runtime-0.5.5878/lib/types/props/decorator.rb:246:in `validate_prop_name': undefined method `match?' for :outdir:Symbol (NoMethodError)

This was introduced in this commit in sorbet-runtime, and there is a Sorbet issue opened.

Seems Ruby 2.3 will be removed from supported versions list.

Generating empty file for `activesupport`

tapioca isn't working for me for activesupport 5.2.3, and a bunch of other gems. It generates an empty file:

# This file is autogenerated. Do not edit it by hand. Regenerate it with:
#   tapioca generate activesupport

# typed: false

I added some puts statements and I found that this was the command that is running here: /Users/alex/.rvm/gems/ruby-2.6.3/gems/sorbet-static-0.5.5258-universal-darwin-18/libexec/sorbet --no-config --print\=symbol-table-json --quiet @/var/folders/zq/bsvhv93151j849l1wtfh7pch0000gn/T/sorbet20200122-23233-fq4mms (the file path is different each time but that doesn't really matter)

Running that command locally I get:

$ /Users/alex/.rvm/gems/ruby-2.6.3/gems/sorbet-static-0.5.5258-universal-darwin-18/libexec/sorbet --no-config --print\=symbol-table-json --quiet @/var/folders/zq/bsvhv93151j849l1wtfh7pch0000gn/T/sorbet20200122-23233-fq4mms
Segmentation fault: 11

And without --quiet I get

$ /Users/alex/.rvm/gems/ruby-2.6.3/gems/sorbet-static-0.5.5258-universal-darwin-18/libexec/sorbet --no-config --print\=symbol-table-json  @/var/folders/zq/bsvhv93151j849l1wtfh7pch0000gn/T/sorbet20200122-23183-9ggl9n
/Users/alex/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.3/lib/active_support/core_ext/module/introspection.rb:13: Unsupported node type Backref https://srb.help/3003
    13 |      parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
                                                  ^^

/Users/alex/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:352: Unsupported node type Backref https://srb.help/3003
     352 |      file_name = $` if file_name =~ /\.rb\z/
                            ^^

/Users/alex/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:402: Unsupported node type Backref https://srb.help/3003
     402 |      path = $` if path =~ /\.rb\z/
                       ^^

/Users/alex/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.3/lib/active_support/testing/isolation.rb:107: include must only contain constant literals https://srb.help/4002
     107 |      include forking_env? ? Forking : Subprocess
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Segmentation fault: 11

For context, srb rbi gems generates an activesupport rbi fine. Is this expected behaviour?

fast generate option?

Is there, or would you consider adding, a way to only generate RBIs for gems that have changed?

Some of the gems I have to use take a long time to require in general, and also a while to generate rbi for, so it'd be nice to skip that work if possible. I understand the need to go through everything if requires change though, so maybe if this was an extra flag to generate rather than the default.

Fix mixes_in_class_methods discovery

The current implementation does not always produce the correct output.

For example, instead of:

mixes_in_class_methods(::ActiveModel::Validations::ClassMethods)

we produce:

mixes_in_class_methods(::ActiveModel::Validations::HelperMethods)

in module ActiveModel::Validations which breaks type-checking.

We need to fix this and come up with a better way of implementing this feature.

How to use `GeneratedPathHelpersModule`?

Hi Tapioca team!

I recently learned about Tapioca from watching Shopify's Shipit! presentation on adopting Sorbet. I was very interested to try it out as my team and I have tried to adopt Sorbet on our large Rails projects in the past but have run into various complications.

First off, I'm happy to say that using Tapioca was much smoother than using Sorbet's built-in tooling! Adding it to a small project went almost entirely smoothly, whereas I could not get past the initial setup with the built-in tools. Thank you for this project!

I do have 1 question, however. In the UrlHelpers docs, it says a GeneratedPathHelpersModule is generated and included into the RBI for any class that includes the dynamic URL helpers module. I see that module is generated in an RBI file, and it is included in the RBI file for my classes.

My question then: How do I use the GeneratedPathHelpersModule module in my class?

Sorbet of course complains about the dynamic include Rails.application.routes.url_helpers, so I need to replace that with something.

But GeneratedPathHelpersModule only exists in the RBI files, so I cannot use include GeneratedPathHelpersModule. I get a NameError when I try that.

How do I replace include Rails.application.routes.url_helpers?

[Feature request] Include sigs from gem rbi files

Hi! I've been really happy using tapioca for my company's codebase, but have run into a snag on more recent versions of sorbet.

We have an internal gem that contains our protobuf definitions and is used by a variety of other apps. If your not familiar, protobufs have a compilation step that take *.proto files which define protobuf messages (essentially structs) and output *_pb.rb files that define corresponding ruby classes. I've been using this protoc plugin to generate rbi files at compile time and putting them in the gem's rbi/ folder.

This had been working pretty well; sorbet picks up the rbi files from the gem and typechecks the proto classes in our apps. The rbis stay in sync because they're auto-generated at compile time.

Unfortunately, a recent change in sorbet prevents sorbet from using gem rbis when tapioca is being used, with the rationale that tapioca copies sigs from gems when you run tapioca generate. As far as I can tell, this is the case for inline sigs, but does not cover our use case of generated rbi files.

I've read some of the discussion on the sorbet slack and understand that this hasn't been a priority to implement, but I want to present this use case where copying gem rbis would be useful.

If you think this is a worthwhile feature and feasible, I'm happy to spend some time working on it.

Support alternative Sorbet install locations

Currently, the path of the sorbet-static binary is hardcoded in lib/tapioca/compilers/sorbet.rb. However, I have a custom build of Sorbet that I use because Sorbet doesn't yet support my M1 mac. However, when I try to run tapioca sync or other tapioca commands, only empty rbi files are generated because tapioca attempts to use a version of Sorbet that crashes on my system.

When running srb commands, there's an escape hatch for this issue: environment variable SRB_SORBET_EXE, which allows one to define a custom path to sorbet-static. Would it be possible for tapioca to support this variable or a similar env var? There are also other escape hatch approaches (e.g., an argument) if we don't want to use environment variables.

I'm happy to make a PR for this change if it's a feature you'd like to add!

Handle inclusions of `T::Props::Plugin`

The following code in a gem:

module Foo
  include T::Props::Constructor
  extend T::Helpers

  module Bar
    include T::Props::ClassMethods
  end

  mixes_in_class_methods Bar
end

Will produce the following RBI with tapioca:

module Foo
  mixes_in_class_methods(::T::Props::Plugin::ClassMethods)
end

module Foo::Bar
end

Because T::Props::Constructor includes T::Props::Plugin which already does:

mixes_in_class_methods(T::Props::Plugin::ClassMethods)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.