Ruby

RBS::Dynamic

RBS::Dynamic is a tool to dynamically analyze Ruby code and generate RBS

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add rbs-dynamic

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install rbs-dynamic

Usage

Execute any Ruby script file with the rbs-dynamic command and generate RBS based on the executed information.

# sample.rb
class FizzBuzz
  def initialize(value)
    @value = value
  end

  def value; @value end

  def apply
      value % 15 == 0 ? "FizzBuzz"
    : value %  3 == 0 ? "Fizz"
    : value %  5 == 0 ? "Buzz"
    : value
  end
end

p (1..20).map { FizzBuzz.new(_1).apply }
$ rbs-dynamic trace sample.rb
# RBS dynamic trace 0.1.0

class FizzBuzz
  private def initialize: (Integer value) -> Integer

  def apply: () -> (Integer | String)

  def value: () -> Integer

  @value: Integer
end
$

NOTE: In this case, no standard output is done when Ruby is executed.

Type supported by rbs-dynamic

  • [x] class / module
    • [x] super class
    • [x] include / prepend / extend
    • [x] instance variables
    • [x] constant variables
    • [ ] class variables
  • [x] Method
    • argument types
    • return type
    • block
    • visibility
    • class methods
  • [x] literal types (e.g. 1 :hoge)
    • String literals are not supported.
  • [x] Generics types
    • [x] Array
    • [x] Hash
    • [x] Range
    • [ ] Enumerable
    • [ ] Struct
  • [ ] Record types
  • [ ] Tuple types

commandline options

$ rbs-dynamic --help trace
Usage:
  rbs-dynamic trace [filename]

Options:
  [--root-path=ROOT-PATH]                                                  # Rooting path. Default: current dir
                                                                           # Default: /home/mayu/Dropbox/work/software/development/gem/rbs-dynamic
  [--target-filepath-pattern=TARGET-FILEPATH-PATTERN]                      # Target filepath pattern. e.g. hoge\|foo\|bar. Default '.*'
                                                                           # Default: .*
  [--ignore-filepath-pattern=IGNORE-FILEPATH-PATTERN]                      # Ignore filepath pattern. Priority over `target-filepath-pattern`. e.g. hoge\|foo\|bar. Default ''
  [--target-classname-pattern=TARGET-CLASSNAME-PATTERN]                    # Target class name pattern. e.g. RBS::Dynamic. Default '.*'
                                                                           # Default: .*
  [--ignore-classname-pattern=IGNORE-CLASSNAME-PATTERN]                    # Ignore class name pattern. Priority over `target-classname-pattern`. e.g. PP\|PrettyPrint. Default ''
  [--ignore-class-members=one two three]
                                                                           # Possible values: inclued_modules, prepended_modules, extended_modules, constant_variables, instance_variables, singleton_methods, methods
  [--method-defined-calsses=one two three]                                 # Which class defines method type. Default: defined_class and receiver_class
                                                                           # Possible values: defined_class, receiver_class
  [--show-method-location], [--no-show-method-location]                    # Show source_location and called_location in method comments. Default: no
  [--use-literal-type], [--no-use-literal-type]                            # Integer and Symbol as literal types. e.g func(:hoge, 42). Default: no
  [--with-literal-type], [--no-with-literal-type]                          # Integer and Symbol with literal types. e.g func(Symbol | :hoge | :foo). Default: no
  [--use-interface-method-argument], [--no-use-interface-method-argument]  # Define method arguments in interface. Default: no
  [--stdout], [--no-stdout]                                                # stdout at runtime. Default: no
  [--trace-c-api-method], [--no-trace-c-api-method]                        # Trace C API method. Default: no

--method-defined-calsses

Specify the class to be defined.

class Base
  def func; end
end

class Sub1 < Base
end

class Sub2 < Base
end

Sub1.new.func
Sub2.new.func
# defined_class and receiver_class
$ rbs-dynamic trace sample.rb
# RBS dynamic trace 0.1.0

class Base
  def func: () -> NilClass
end

class Sub1 < Base
  def func: () -> NilClass
end

class Sub2 < Base
  def func: () -> NilClass
end
$
# only defined class
$ rbs-dynamic trace sample.rb --method-defined-calsses=defined_class
# RBS dynamic trace 0.1.0

class Base
  def func: () -> NilClass
end
# only receiver class
$ rbs-dynamic trace sample.rb --method-defined-calsses=receiver_class
# RBS dynamic trace 0.1.0

class Base
end

class Sub1 < Base
  def func: () -> NilClass
end

class Sub2 < Base
  def func: () -> NilClass
end

--show-method-location

Add method definition location and reference location to comments.

# sample.rb
class X
  def func1(a)
  end

  def func2
    func1(42)
  end
end

x = X.new
x.func1("homu")
x.func2
$ rbs-dynamic trace sample.rb --show-method-location
# RBS dynamic trace 0.1.0

class X
  # source location: sample.rb:2
  # reference location:
  #   func1(String a) -> NilClass sample.rb:11
  #   func1(Integer a) -> NilClass sample.rb:6
  def func1: (String | Integer a) -> NilClass

  # source location: sample.rb:5
  # reference location:
  #   func2() -> NilClass sample.rb:12
  def func2: () -> NilClass
end
$

--use-literal-type

Use Symbol literal or Integer literal as type.

# sample.rb
class X
  def func(a)
    a.to_s
  end
end

x = X.new
x.func(1)
x.func(2)
x.func(:hoge)
x.func(:foo)
# Not used options
$ ./exe/rbs-dynamic trace sample.rb
# RBS dynamic trace 0.1.0

class X
  def func: (Integer | Symbol a) -> String
end
$
# Used options
$ rbs-dynamic trace sample.rb --use-literal-type
# RBS dynamic trace 0.1.0

class X
  def func: (1 | 2 | :hoge | :foo a) -> String
end
rbs-dynamic $
$

--with-literal-type

Use Symbol literal or Integer literal as type and union original type

# sample.rb
class X
  def func(a)
    a.to_s
  end

  def func2(a)
  end
end

x = X.new
x.func(1)
x.func(2)
x.func(:hoge)
x.func(:foo)
x.func2({ id: 1, name: "homu", age: 14 })
$ rbs-dynamic trace sample.rb --with-literal-type
# RBS dynamic trace 0.1.0

class X
  def func: (Integer | Symbol | 1 | 2 | :hoge | :foo a) -> String

  def func2: (Hash[Symbol | :id | :name | :age, Integer | String | 1 | 14] a) -> NilClass
end
$

--use-interface-method-argument

Define and use interface type.

# sample.rb
class Output
  def my_puts(a)
    puts a.to_s
  end
end

class Cat
  def to_s
    "Cat"
  end
end

class Dog
  def to_s
    "Dog"
  end
end

output = Output.new

output.my_puts Cat.new
output.my_puts Dog.new
$ rbs-dynamic trace sample.rb --use-interface-method-argument
# RBS dynamic trace 0.1.0

class Output
  def my_puts: (_Interface_have__to_s__1 a) -> NilClass

  interface _Interface_have__to_s__1
    def to_s: () -> String
  end
end

class Cat
  def to_s: () -> String
end

class Dog
  def to_s: () -> String
end
$

Development

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

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

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/osyo-manga/gem-rbs-dynamic.