Git Product home page Git Product logo

rbs's Introduction

RBS

RBS is a language to describe the structure of Ruby programs. You can write down the definition of a class or module: methods defined in the class, instance variables and their types, and inheritance/mix-in relations. It also allows declaring constants and global variables.

The following is a small example of RBS for a chat app.

module ChatApp
  VERSION: String

  class User
    attr_reader login: String
    attr_reader email: String

    def initialize: (login: String, email: String) -> void
  end

  class Bot
    attr_reader name: String
    attr_reader email: String
    attr_reader owner: User

    def initialize: (name: String, owner: User) -> void
  end

  class Message
    attr_reader id: String
    attr_reader string: String
    attr_reader from: User | Bot                     # `|` means union types: `#from` can be `User` or `Bot`
    attr_reader reply_to: Message?                   # `?` means optional type: `#reply_to` can be `nil`

    def initialize: (from: User | Bot, string: String) -> void

    def reply: (from: User | Bot, string: String) -> Message
  end

  class Channel
    attr_reader name: String
    attr_reader messages: Array[Message]
    attr_reader users: Array[User]
    attr_reader bots: Array[Bot]

    def initialize: (name: String) -> void

    def each_member: () { (User | Bot) -> void } -> void  # `{` and `}` means block.
                   | () -> Enumerator[User | Bot, void]   # Method can be overloaded.
  end
end

The Target Version

  • The standard library signatures targets the latest release of Ruby. (3.2 as of 2023.)
  • The library code targets non-EOL versions of Ruby. (>= 3.0 as of 2023.)

Installation

Install the rbs gem. $ gem install rbs from the command line, or add a line in your Gemfile.

gem "rbs"

CLI

The gem ships with the rbs command line tool to demonstrate what it can do and help develop RBS.

$ rbs version
$ rbs list
$ rbs ancestors ::Object
$ rbs methods ::Object
$ rbs method Object then

An end user of rbs will probably find rbs prototype the most useful. This command generates boilerplate signature declarations for ruby files. For example, say you have written the below ruby script.

# person.rb
class Person
  attr_reader :name
  attr_reader :contacts

  def initialize(name:)
    @name = name
    @contacts = []
  end

  def speak
    "I'm #{@name} and I love Ruby!"
  end
end

Running prototype on the above will automatically generate

$ rbs prototype rb person.rb
class Person
  @name: untyped

  @contacts: untyped

  attr_reader name: untyped

  attr_reader contacts: untyped

  def initialize: (name: untyped) -> void

  def speak: () -> ::String
end

It prints signatures for all methods, classes, instance variables, and constants. This is only a starting point, and you should edit the output to match your signature more accurately.

rbs prototype offers three options.

  • rb generates from just the available Ruby code
  • rbi generates from Sorbet RBI
  • runtime generates from runtime API

Library

There are two important concepts, environment and definition.

An environment is a dictionary that keeps track of all declarations. What is the declaration associated with String class? An environment will give you the answer.

A definition gives you the detail of the class. What is the type of the return value of gsub method of the String class? The definition for String class knows the list of methods it provides and their types.

The following is a small code to retrieve the definition of the String#gsub method.

require "rbs"

loader = RBS::EnvironmentLoader.new()

# loader.add(path: Pathname("sig"))   # Load .rbs files from `sig` directory
# loader.add(library: "pathname")     # Load pathname library

environment = RBS::Environment.from_loader(loader).resolve_type_names

# ::String
string = RBS::TypeName.new(name: :String, namespace: RBS::Namespace.root)

# Class declaration for ::String
decl = environment.class_decls[string]

# Builder provides the translation from `declaration` to `definition`
builder = RBS::DefinitionBuilder.new(env: environment)

# Definition of instance of String
instance = builder.build_instance(string)

# Print the types of `gsub` method:
puts instance.methods[:gsub].method_types.join("\n")
# Outputs =>
#  (::Regexp | ::string pattern, ::string replacement) -> ::String
#  (::Regexp | ::string pattern, ::Hash[::String, ::String] hash) -> ::String
#  (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String
#  (::Regexp | ::string pattern) -> ::Enumerator[::String, self]

# Definition of singleton of String
singleton = builder.build_singleton(string)
# No `gsub` method for String singleton
puts singleton.methods[:gsub]

Guides

Community

Here is a list of some places you can talk with active maintainers.

Development

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

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

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/rbs.

rbs's People

Contributors

afront avatar ahazem avatar aikyo02 avatar buzztaiki avatar connorshea avatar dependabot[bot] avatar hanachin avatar hjwylde avatar honeyryderchuck avatar hsbt avatar kachick avatar ksss avatar m11o avatar mame avatar marcandre avatar mihyaeru21 avatar mtsmfm avatar natsuokawai avatar nobu avatar paradoxv5 avatar pocke avatar raosush avatar sampersand avatar snaka avatar soutaro avatar taki avatar tk0miya avatar waka avatar ybiquitous avatar ydah 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

rbs's Issues

Methods that start with an underscore are interpreted by the parser as interfaces

class Foo
  def self._bar?(baz)
    return baz
  end
end

puts Foo._bar?('qux') #=> 'qux'

I noticed this when running bundle exec rbs parse bundler.rbs for a bundler.rbs created from sorbet-typed's bundler.rbi. Bundler has a method called _path_to?, so this definitely happens in actual Ruby code (though it's probably rare).

The RBS file:

class Foo
  def self._path_to?: (untyped baz) -> untyped
end

rbs parse foo.rbs gets me this error:

foo.rbs:2:11: parse error on value: (tINTERFACEIDENT)

[doc] Outdated Entry in Hash

# Hashes are also commonly used as a way to have named parameters in functions.
# Note that no brackets are used below. If a hash is the last argument on a
# method call, no braces are needed, thus creating a really clean interface:
#
# Person.create(name: "John Doe", age: 27)
#
# def self.create(params)
# @name = params[:name]
# @age = params[:age]
# end

Doc entry at the linked line isn't compliant with ruby 2.7 anymore... I'm not really sure what the best way to present the alternative would be.

`rbs prototype runtime` didn't work with a class including anonymous modules

With class C in ./c.rb

class C
  include Module.new
end

this command fails.

$ bundle2.7 exec rbs prototype runtime --require ./c C
Traceback (most recent call last):
	13: from /home/tadashi/git/rbs/vendor/bundle/ruby/2.7.0/bin/rbs:23:in `<main>'
	12: from /home/tadashi/git/rbs/vendor/bundle/ruby/2.7.0/bin/rbs:23:in `load'
	11: from /home/tadashi/git/rbs/exe/rbs:7:in `<top (required)>'
	10: from /home/tadashi/git/rbs/lib/rbs/cli.rb:85:in `run'
	 9: from /home/tadashi/git/rbs/lib/rbs/cli.rb:455:in `run_prototype'
	 8: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:40:in `decls'
	 7: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:40:in `each'
	 6: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:43:in `block in decls'
	 5: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:323:in `generate_class'
	 4: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:64:in `each_mixin'
	 3: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:64:in `each'
	 2: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:66:in `block in each_mixin'
	 1: from /home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:329:in `block in generate_class'
/home/tadashi/git/rbs/lib/rbs/prototype/runtime.rb:53:in `to_type_name': undefined method `split' for nil:NilClass (NoMethodError)

This is triggered by the below line because anonymous modules have a name nil

*prefix, last = name.split(/::/)

but I don't know what to do, so I submitted this issue.

Side-note: This always happened when I applied rbs prototype runtime SomeController to a Rails application controller. Since controllers, at least with Rails 6.0.3.1, seem to always include an anonymous module, so the execution fails all the time.

NoMethodError if too many type arguments passed for a type

If you define a class which takes type arguments, and then pass it too many type arguments, RBS throws a NoMethodError from something being nil when using rbs validate.

For example, using this RBS:

module Example
  class Foo[X]
  end

  def example: (Foo[String, Integer] x) -> void
end

Raises this error:

NoMethodError: undefined method `variance' for nil:NilClass
  /home/aaron/src/rbs/lib/rbs/variance_calculator.rb:123:in `block in type'

Passing too few type arguments (such as by just using Foo) will result in a proper InvalidTypeApplicationError type error, which is probably what passing too many type arguments should also do.

log_device.rbs issues

Dearest maintainer,

I am reading over https://github.com/ruby/rbs/blob/master/stdlib/logger/log_device.rbs#L15 this line here.

def reopen: (?logdev log) -> self

Is this parameter valid syntax? I would expect it to be Logdev.

Second I am unsure what Logdev is. I think based off the comment https://github.com/ruby/logger/blob/master/lib/logger.rb#L405

def reopen: (String | IO | nil) -> self

might make more sense but I am unsure. I am aslo not 100% on the correct syntax. I can make the edits if this is correct.

Thank you for you work and time,

Dictated but not reviewed,
Becker

Request for change: type alias

I am unsure if this is the form to request for changes to rbs. I am sure there is prior thought and discussion of alias types but I could not find them directly.

  • Feature Name: Alias type changes

Summary

The rbs syntax allows for alias types. An alias type is a short hand for a grouping of ruby types that can be used in place of ruby types. Example type string = String | _ToStr allows any String type and anything that responds to to_s. This system allows for less typing by the user and easier types.

I would like propose a few changes to the current system and ask a few thing.

  1. Type alias should be prepend with a character.
    example type ~string = String | _ToString and its use would be def foo(~string string). The reason for this change is related to readability of intent. When allowing just lower case it is unclear if string should have been String or that it was. It is clear form the extra character that this value is suppose to be a type alias.

  2. Discoverability should be built in to the type alias.
    I dont know the answer to this one but using non-ruby types from other files can seem like magic global variables. I know a file will use external type and these feel different because a developer can use irb to inspect RandomClass. There is no way to locate these types if they are real or not. For example see my issue around logdev #377 and the current logdev search https://github.com/ruby/rbs/search?q=logdev&unscoped_q=logdev . You can also see my confusion on this issue #378. The search did not return the line for type logdev = _WriteCloser | String.

The first idea I have is that you can add a from usage type-from: stdlib:~string. type-from: gem:json:~string type-from: super:~string stdlib, gem and super can be use to denote ruby built in, gems and the local repro. this helps with scoping of names over different code.

  1. Regardless of changes here alias types for the stdlib need to be a bold statement in the documentation and syntax.
    A full section of the stdlib types and their use cases so that everyone knows to start using them from the start will lead to better adoption.

Motivation

The motivation is to make adoption clear and easy. The less code reviews asking if this is a type or where the alias type comes from makes a developers review process much easier. It is also easier to parse if type alias has its own syntax. When I say parse I do mean visually and computationally. The discoverability nudges people to use the correct values (~string vs String)

Reference-level explanation

I believe this will work well with the other features of rbs. The parser and related code examples will need to be updated. The type resolver might be much harder to implement. You would need to track rbs file based on location (local, stdlib, gem) and correctly use its alias.

Drawbacks

I do not love typing ~. Its so far to the side and requires a shift. Bikeshedding some other characters @ I do not love it because it is used in ruby as a variable. # looks ok.

Rationale and alternatives

prior-art sorbet uses class syntax. I do not think this is a bad idea but what would string become? Stringy? I think there is a chance for class name space issues.

Prior art

rbs to rbi example
https://github.com/Shopify/rbs_parser/blob/master/specs/README.md#type-alias-declaration

rbi type alias docs
https://sorbet.org/docs/type-aliases

A commit around type parsing.
abf1fd4

lib/ruby/signature/parser.y kTYPE

Unresolved questions

Future possibilities

Once again thank you for your time and work on this project,

Dictated but not reviewed,
Becker

PS: I borrowed this template from rust rfc https://raw.githubusercontent.com/rust-lang/rfcs/master/0000-template.md

RubyVM usage

Hello, it seems that currently rbs uses RubyVM::AbstractSyntaxTree in a couple places:
https://github.com/ruby/rbs/search?q=RubyVM&unscoped_q=RubyVM

However, RubyVM by design is MRI-only and is expected to not exist as a constant on alternative Ruby implementations (documentation).

Do you plan to address this somehow?
If AbstractSyntaxTree is needed by rbs then I think it's time to move it somewhere outside of RubyVM.

For instance, when running on TruffleRuby:

% rbs prototype rb lib/person.rb lib/email.rb lib/phone.rb
/Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/lib/rbs/prototype/rb.rb:55:in `const_missing': uninitialized constant RBS::Prototype::RB::RubyVM (NameError)
	from /Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/lib/rbs/prototype/rb.rb:55:in `parse'
	from /Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/lib/rbs/cli.rb:629:in `block in run_prototype_file'
	from /Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/lib/rbs/cli.rb:628:in `each'
	from /Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/lib/rbs/cli.rb:628:in `run_prototype_file'
	from /Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/lib/rbs/cli.rb:526:in `run_prototype'
	from /Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/lib/rbs/cli.rb:92:in `run'
	from /Users/bfish/Documents/truffleruby-ws/truffleruby/mxbuild/truffleruby-native/jre/languages/ruby/lib/gems/gems/rbs-0.7.0/exe/rbs:7:in `<top (required)>'
	from <internal:core> core/kernel.rb:395:in `load'
	from <internal:core> core/kernel.rb:395:in `load'
	from /Users/bfish/.rbenv/versions/tr-native/bin/rbs:23:in `<main>'

cc @bjfish

Relates to oracle/truffleruby#1671

ruby-head builds fail

2.6.3

% git rev-parse HEAD
bba1b272395c4027603ba69c0224ed7fc5bee1a3
% ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]
% bundle exec rake

# Running tests with run options --seed 27536:

..................................................................................................................D, [2019-10-16T22:25:47.134749 #11139] DEBUG -- : #foo receives arguments: []
D, [2019-10-16T22:25:47.134805 #11139] DEBUG -- : #foo receives block arguments: [123]
D, [2019-10-16T22:25:47.134823 #11139] DEBUG -- : #foo returns from block: 3
D, [2019-10-16T22:25:47.134835 #11139] DEBUG -- : #foo returns: "3"
E, [2019-10-16T22:25:47.134916 #11139] ERROR -- : [Foo#foo] ArgumentError: expected method type (::String x, ::Integer i, foo: 123 foo) { (Integer) -> Array[Integer] } -> ::String
E, [2019-10-16T22:25:47.134935 #11139] ERROR -- : [Foo#foo] BlockReturnTypeError: expected `Array[Integer]` but returns `3`
D, [2019-10-16T22:25:47.134953 #11139] DEBUG -- : #foo receives arguments: ["", 3, {:foo=>234}]
D, [2019-10-16T22:25:47.134968 #11139] DEBUG -- : #foo receives block arguments: [123]
D, [2019-10-16T22:25:47.134979 #11139] DEBUG -- : #foo returns from block: nil
D, [2019-10-16T22:25:47.134986 #11139] DEBUG -- : #foo returns: ""
E, [2019-10-16T22:25:47.135045 #11139] ERROR -- : [Foo#foo] ArgumentTypeError: expected `123` (foo) but given `234`
E, [2019-10-16T22:25:47.135059 #11139] ERROR -- : [Foo#foo] BlockReturnTypeError: expected `Array[Integer]` but returns `nil`
D, [2019-10-16T22:25:47.135068 #11139] DEBUG -- : #foo receives arguments: []
D, [2019-10-16T22:25:47.135078 #11139] DEBUG -- : #foo returns: :foo
E, [2019-10-16T22:25:47.135112 #11139] ERROR -- : [Foo#foo] ArgumentError: expected method type (::String x, ::Integer i, foo: 123 foo) { (Integer) -> Array[Integer] } -> ::String
E, [2019-10-16T22:25:47.135125 #11139] ERROR -- : [Foo#foo] ReturnTypeError: expected `::String` but returns `:foo`
E, [2019-10-16T22:25:47.135147 #11139] ERROR -- : [Foo#foo] MissingBlockError: required block is missing for `(::String x, ::Integer i, foo: 123 foo) { (Integer) -> Array[Integer] } -> ::String`
.D, [2019-10-16T22:25:47.138107 #11139] DEBUG -- : .open receives arguments: []
D, [2019-10-16T22:25:47.138422 #11139] DEBUG -- : .open receives block arguments: [#<#<Class:0x00007fca37d930b0>:0x00007fca37d9b698>]
D, [2019-10-16T22:25:47.138445 #11139] DEBUG -- : .open returns from block: 6
D, [2019-10-16T22:25:47.138698 #11139] DEBUG -- : .open returns: #<#<Class:0x00007fca37d930b0>:0x00007fca37d9b698>
E, [2019-10-16T22:25:47.138971 #11139] ERROR -- : [Foo.open] ReturnTypeError: expected `::String` but returns `#<#<Class:0x00007fca37d930b0>:0x00007fca37d9b698>`
E, [2019-10-16T22:25:47.139181 #11139] ERROR -- : [Foo.open] BlockArgumentTypeError: expected `::String` but given `#<#<Class:0x00007fca37d930b0>:0x00007fca37d9b698>`
.I, [2019-10-16T22:25:47.143138 #11139]  INFO -- : Installing hook on ::Foo#foo: (*any) -> ::String
I, [2019-10-16T22:25:47.143750 #11139]  INFO -- : Installing hook on ::Foo.open: () { (::String) -> void } -> ::Integer
I, [2019-10-16T22:25:47.143784 #11139]  INFO -- : Installing hook on ::Foo.new: () -> ::Foo
D, [2019-10-16T22:25:47.143806 #11139] DEBUG -- : .open receives arguments: []
D, [2019-10-16T22:25:47.143828 #11139] DEBUG -- : .new receives arguments: []
D, [2019-10-16T22:25:47.143844 #11139] DEBUG -- : .new returns: #<Foo:0x00007fca37e09648>
D, [2019-10-16T22:25:47.143875 #11139] DEBUG -- : .open receives block arguments: [""]
D, [2019-10-16T22:25:47.143890 #11139] DEBUG -- : #foo receives arguments: [1, 2, 3]
D, [2019-10-16T22:25:47.143902 #11139] DEBUG -- : #foo returns: "hello foo"
D, [2019-10-16T22:25:47.143925 #11139] DEBUG -- : .open returns from block: "hello foo"
D, [2019-10-16T22:25:47.143934 #11139] DEBUG -- : .open returns: 1
.

Finished tests in 11.731012s, 9.9736 tests/s, 70.8379 assertions/s.


117 tests, 831 assertions, 0 failures, 0 errors, 0 skips
Run options: --seed 57484

# Running:

.......

Finished in 0.001997s, 3505.2578 runs/s, 7010.5155 assertions/s.

7 runs, 14 assertions, 0 failures, 0 errors, 0 skips

master

% git rev-parse HEAD
bba1b272395c4027603ba69c0224ed7fc5bee1a3
% ruby -v
ruby 2.7.0dev (2019-10-16T12:00:36Z master f8fb51c976) [x86_64-darwin19]
% bundle exec rake

# Running tests with run options --seed 25745:

....EFEEEEEE.FFE.FF.E...........D, [2019-10-16T22:26:43.981743 #11223] DEBUG -- : .open receives arguments: []
D, [2019-10-16T22:26:43.981875 #11223] DEBUG -- : .open receives block arguments: [#<#<Class:0x00007f9ea7a0c088>:0x00007f9ea79fe898>]
D, [2019-10-16T22:26:43.981927 #11223] DEBUG -- : .open returns from block: 6
D, [2019-10-16T22:26:43.981962 #11223] DEBUG -- : .open returns: #<#<Class:0x00007f9ea7a0c088>:0x00007f9ea79fe898>
E, [2019-10-16T22:26:43.982130 #11223] ERROR -- : [Foo.open] ReturnTypeError: expected `::String` but returns `#<#<Class:0x00007f9ea7a0c088>:0x00007f9ea79fe898>`
E, [2019-10-16T22:26:43.982174 #11223] ERROR -- : [Foo.open] BlockArgumentTypeError: expected `::String` but given `#<#<Class:0x00007f9ea7a0c088>:0x00007f9ea79fe898>`
.D, [2019-10-16T22:26:43.989417 #11223] DEBUG -- : #foo receives arguments: []
D, [2019-10-16T22:26:43.989497 #11223] DEBUG -- : #foo receives block arguments: [123]
D, [2019-10-16T22:26:43.989536 #11223] DEBUG -- : #foo returns from block: 3
D, [2019-10-16T22:26:43.989567 #11223] DEBUG -- : #foo returns: "3"
E, [2019-10-16T22:26:43.989732 #11223] ERROR -- : [Foo#foo] ArgumentError: expected method type (::String x, ::Integer i, foo: 123 foo) { (Integer) -> Array[Integer] } -> ::String
E, [2019-10-16T22:26:43.989778 #11223] ERROR -- : [Foo#foo] BlockReturnTypeError: expected `Array[Integer]` but returns `3`
D, [2019-10-16T22:26:43.989823 #11223] DEBUG -- : #foo receives arguments: ["", 3, {:foo=>234}]
D, [2019-10-16T22:26:43.989891 #11223] DEBUG -- : #foo receives block arguments: [123]
D, [2019-10-16T22:26:43.989925 #11223] DEBUG -- : #foo returns from block: nil
D, [2019-10-16T22:26:43.989952 #11223] DEBUG -- : #foo returns: ""
E, [2019-10-16T22:26:43.990121 #11223] ERROR -- : [Foo#foo] ArgumentTypeError: expected `123` (foo) but given `234`
E, [2019-10-16T22:26:43.990163 #11223] ERROR -- : [Foo#foo] BlockReturnTypeError: expected `Array[Integer]` but returns `nil`
D, [2019-10-16T22:26:43.990193 #11223] DEBUG -- : #foo receives arguments: []
D, [2019-10-16T22:26:43.990224 #11223] DEBUG -- : #foo returns: :foo
E, [2019-10-16T22:26:43.990322 #11223] ERROR -- : [Foo#foo] ArgumentError: expected method type (::String x, ::Integer i, foo: 123 foo) { (Integer) -> Array[Integer] } -> ::String
E, [2019-10-16T22:26:43.990361 #11223] ERROR -- : [Foo#foo] ReturnTypeError: expected `::String` but returns `:foo`
E, [2019-10-16T22:26:43.990432 #11223] ERROR -- : [Foo#foo] MissingBlockError: required block is missing for `(::String x, ::Integer i, foo: 123 foo) { (Integer) -> Array[Integer] } -> ::String`
..I, [2019-10-16T22:26:44.006327 #11223]  INFO -- : Installing hook on ::Foo#foo: (*any) -> ::String
I, [2019-10-16T22:26:44.008209 #11223]  INFO -- : Installing hook on ::Foo.open: () { (::String) -> void } -> ::Integer
I, [2019-10-16T22:26:44.008272 #11223]  INFO -- : Installing hook on ::Foo.new: () -> ::Foo
D, [2019-10-16T22:26:44.008322 #11223] DEBUG -- : .open receives arguments: []
D, [2019-10-16T22:26:44.008386 #11223] DEBUG -- : .new receives arguments: []
D, [2019-10-16T22:26:44.008428 #11223] DEBUG -- : .new returns: #<Foo:0x00007f9ea624d208>
D, [2019-10-16T22:26:44.008499 #11223] DEBUG -- : .open receives block arguments: [""]
D, [2019-10-16T22:26:44.008539 #11223] DEBUG -- : #foo receives arguments: [1, 2, 3]
D, [2019-10-16T22:26:44.008595 #11223] DEBUG -- : #foo returns: "hello foo"
D, [2019-10-16T22:26:44.008665 #11223] DEBUG -- : .open returns from block: "hello foo"
D, [2019-10-16T22:26:44.008694 #11223] DEBUG -- : .open returns: 1
..................................................................................

Finished tests in 35.765481s, 3.2713 tests/s, 23.0110 assertions/s.


Error:
Ruby::Signature::RbiScaffoldTest#test_constant:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:220:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:114:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:79:in `push_module'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:113:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:113:in `test_constant'

Failure:
Ruby::Signature::RbiScaffoldTest#test_alias [/Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:137]
Minitest::Assertion: --- expected
+++ actual
@@ -1,5 +1,5 @@
 "module Foo
-  alias foo Bar
+  alias  
 
   alias hello world
 end

Error:
Ruby::Signature::RbiScaffoldTest#test_comment:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:352:in `test_comment'

Error:
Ruby::Signature::RbiScaffoldTest#test_tuple:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:214:in `test_tuple'

Error:
Ruby::Signature::RbiScaffoldTest#test_basic_object:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:318:in `test_basic_object'

Error:
Ruby::Signature::RbiScaffoldTest#test_1:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:31:in `test_1'

Error:
Ruby::Signature::RbiScaffoldTest#test_class_of:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:280:in `test_class_of'

Error:
Ruby::Signature::RbiScaffoldTest#test_block_args:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:162:in `test_block_args'

Failure:
Ruby::Signature::RbiScaffoldTest#test_all [/Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:248]
Minitest::Assertion: --- expected
+++ actual
@@ -1,4 +1,4 @@
 "class File
-  def self.split: (String & Integer file) -> void
+  def self.split: (any file) -> void
 end
 "

Failure:
Ruby::Signature::RbiScaffoldTest#test_non_parameter_type_member [/Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:405]
Minitest::Assertion: --- expected
+++ actual
@@ -1,4 +1,3 @@
-"class Dir
-  include Enumerable
+"class Dir[Elem]
 end
 "

Error:
Ruby::Signature::RbiScaffoldTest#test_noreturn:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:168:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:258:in `test_noreturn'

Failure:
Ruby::Signature::RbiScaffoldTest#test_overloading [/Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:201]
Minitest::Assertion: --- expected
+++ actual
@@ -1,7 +1,7 @@
 "class Class
   def initialize: () -> void
-                | (?Class superclass) -> void
-                | () { (Class arg0) -> any } -> void
-                | (?Class superclass) { (Class arg0) -> any } -> void
+                | () -> void
+                | () -> void
+                | () -> void
 end
 "

Failure:
Ruby::Signature::RbiScaffoldTest#test_parameter [/Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:308]
Minitest::Assertion: --- expected
+++ actual
@@ -1,4 +1,3 @@
 "class Array[Elem]
-  include Enumerable
 end
 "

Error:
Ruby::Signature::RbiScaffoldTest#test_bool:
NoMethodError: undefined method `type' for nil:NilClass
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:420:in `type_of0'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:406:in `type_of'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:276:in `method_type'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `map'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:188:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:109:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:61:in `push_class'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:108:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:242:in `block in process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:546:in `block in each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:544:in `each_child'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:241:in `process'
    /Users/sei/src/github.com/ruby/ruby-signature/lib/ruby/signature/scaffold/rbi.rb:35:in `parse'
    /Users/sei/src/github.com/ruby/ruby-signature/test/ruby/signature/rbi_scaffold_test.rb:335:in `test_bool'

117 tests, 823 assertions, 5 failures, 9 errors, 0 skips

Originally posted by @hanachin in #30 (comment)

Feature Request: class/module alias

Problem

I'd like to use aliases for classes/modules, but currently RBS doesn't have the feature.

# Ruby code
class Foo
end

Foo2 = Foo

In this case, I don't have a good idea to declare the type of Foo2.

First try: singleton(Foo)

First, I tried to use singleton(Foo).

# RBS
class Foo
end

Foo2: singleton(Foo)

The type of Foo2 is correct, but it has a problem. The Foo2 isn't inheritable.

# RBS
class Foo
end

Foo2: singleton(Foo)

# Could not find super class: Foo2 (RBS::NoSuperclassFoundError)
class Foo3 < Foo2
end

Another try: inherit Foo

Then, I tried to use inheritance. For example:

# RBS
class Foo
end

class Foo2 < Foo
end

# No error
class Foo3 < Foo2
end

It works. But I think the inheritance introduces another problem because Foo2 != Foo.

# RBS
class Foo
end

class Foo2 < Foo
end

class C
  def m: (f Foo2) -> untyped
end

I expect C#m receives Foo also because Foo2 is an alias of Foo. But actually the method type declaration rejects Foo as an argument.

Problem in the real world

I noticed this problem when I generated types for Ruby on Rails.
It has several class aliases. For example, HashWithIndifferentAccess is an alias of ActiveSupport::HashWithIndifferentAccess.

And we have the same problem in the standard libraries. For example, we can find a class alias from optparse library.
https://github.com/ruby/ruby/blob/361db567ac0c2566b81c001121e1c20d8a3415a3/lib/optparse.rb#L2234-L2235

[docs] missing statement clairfications

Dearest Maintainer,

Record type in the syntax doc https://github.com/ruby/rbs/blob/master/docs/syntax.md#record-type where it is part of type

         | `{` _record-name_ `:` _type_ `,` ... `}`     (Record type)

uses record-name which is undefined. I add a few questions about what is allowed here.

  1. Only symbol version of hashes are allowed to be used and not hashrockets (=>) ?

  2. any valid ruby hash symbol is allowed? included but not limited to {x: 1, X: 1, "y":1}

  3. record-name can not be a class? {{x:Int}: Int} as an example?

Second for literal string, symbol and integer are not defined.

_literal_ ::= _string-literal_
            | _symbol-literal_
            | _integer-literal_
            | `true`
            | `false`

1 Does integer-literal cover any ruby supported number? Float, Hex(0xa) and a longer list I dont normally use every day?

I think I have an understand of what can be a symbol and string.

Once again, thank you for your work and time on this project.

Dictated but not reviewed,
Becker

inherit signature from another method

A pattern used a bit in the wild is the delegation of arguments to another method:

def perform(*args)
  # log things, activate gear
  do_perform(*args)
end

def do_perform(key, value, description)
   ....

Considering that add typing info to do_perform is straightforward, how could I hint in rbs that perform has the same signature? If not, does it make sense to have such a feature?

Blocks set to T.untyped aren't converted properly

Given a Sorbet foo.rbi with a single method:

# typed: true
module Foo
  sig { params(blk: T.untyped).void }
  def bar(&blk); end
end

It returns this when I run bundle exec rbs prototype rbi foo.rbi:

Unexpected block type: untyped
(SCOPE@4:2-4:20
 tbl: [:blk]
 args:
   (ARGS@4:10-4:14
    pre_num: 0
    pre_init: nil
    opt: nil
    first_post: nil
    post_num: 0
    post_init: nil
    rest: nil
    kw: nil
    kwrest: nil
    block: :blk)
 body: nil)
# typed: true
module Foo
  def bar: () { () -> untyped } -> void
end

It should handle a block type set to T.untyped without erroring.

def bar: () { () -> untyped } -> void is the correct method signature, so it kind of already handles it correctly.

Please tell me if these kinds of issues shouldn't be opened yet while ruby-signature is still in development, I just figured I should report it :)

CI & Sider

Sider tests look nice, but the current codebase should pass its tests...

For example I modified a line containing arg0 and got an error because of that...

It seems to check only the diff, which seems like the wrong way to go.

I'm not sure how to have it urn on everything, let me know if you need help.

Improve core/stdlib signature coverage

We provide the signatures of a very limited number of standard library classes. We will really appreciate if you write the signature of other classes.

(Note that we are currently working for standard library signatures.)

NoMethodError via `rbs prototype runtime -r rexml/document REXML`

When I try getting prototype of the REXML type via rbs prototype, I find that the following NoMethodError occurs:

/usr/local/lib/ruby/gems/2.7.0/gems/rexml-3.2.4/lib/rexml/functions.rb:89:in `get_namespace': undefined method `[]' for nil:NilClass (NoMethodError)

The cause seems to me that REXML::Functions.name redefines Module.name:

https://github.com/ruby/rexml/blob/v3.2.4/lib/rexml/functions.rb#L80

Is there a way to avoid this error?

Reproduction

$ docker run --rm -it rubylang/ruby:2.7.2-bionic bash
(...)

$ ruby -v
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]

$ gem install rexml rbs
(...)

$ ruby -r rexml/document -e 'puts REXML::VERSION'
3.2.4

$ rbs -v
rbs 0.14.0

$ rbs prototype runtime -r rexml/document REXML
Traceback (most recent call last):
	14: from /usr/local/bin/rbs:23:in `<main>'
	13: from /usr/local/bin/rbs:23:in `load'
	12: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/exe/rbs:7:in `<top (required)>'
	11: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/cli.rb:98:in `run'
	10: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/cli.rb:590:in `run_prototype'
	 9: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:40:in `decls'
	 8: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:40:in `select'
	 7: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:40:in `each'
	 6: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:40:in `each_object'
	 5: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:40:in `block in decls'
	 4: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:20:in `target?'
	 3: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:20:in `any?'
	 2: from /usr/local/lib/ruby/gems/2.7.0/gems/rbs-0.14.0/lib/rbs/prototype/runtime.rb:24:in `block in target?'
	 1: from /usr/local/lib/ruby/gems/2.7.0/gems/rexml-3.2.4/lib/rexml/functions.rb:81:in `name'
/usr/local/lib/ruby/gems/2.7.0/gems/rexml-3.2.4/lib/rexml/functions.rb:89:in `get_namespace': undefined method `[]' for nil:NilClass (NoMethodError)

Environment

  • Ruby 2.7.2
  • RBS 0.14.0
  • REXML 3.2.4

Preprocessor support for stdlib RBSs

Writing RBS files, especially for standard libraries, requires a lot of duplications. (Example: File.open, Pathname#open, Kernel#open, CSV.open, ...) They are sometimes essentially depending types of other methods.

We know the problem but also decided not to extend RBS syntax for this purpose, because most of the cases are standard libraries and we don't want to make the syntax much more complex.

The plan-B is using preprocessors!

Considerations:

  • Defining APIs (DSL) for describing types.
    • type_of("File.open").keywords.except(:encoding).merge(chdir: type("String")) ...???
  • The compilation to preprocessed files. (We want to ship the preprocessed version for production.)
    • We don't want to do rake precompile every time when developing.
  • Can it be public API for end users?

[docs] parameters var-name usage

Dearest Maintainer,

I fixed a typo in the syntax file and asked a question (#369). I dont think I full understand the answer.

The example given was

def foo: (String name, email: String address) -> void

I am wondering what the ruby code looks like for this signature. Would it look like this?

def foo(name, email: )
  # somecode
end

foo("sbecker", email: "github@domain")

The syntax file https://github.com/ruby/rbs/blame/master/docs/syntax.md#L215 says "parameter with var name". What is the use of the var name? Is it checked against the implementation? If I change foo's implementation variable name is the signature valid? Does a change in variable name need to propagate to the rbs file if the type does not change? Is it a hint to the user via an error message?

1 concern is I dont know what it means to me. I can see it being confusing and writing foo(name: "sbeckeriv", email: github@domain") because it looks like a keyword param.

2 concern is code drift. This will happen no matter what with changes to param types or keywords. I can see the lazy version of me not updating name in the rbs file or forgetting to do it. this means another linter is in my future.

3 concern is correctness. Are these two signatures the same? What if any are the differences.

def foo: (String name, email: String address) -> void
def foo: (String, email: String address) -> void

Once again, thank you for your work and time reviewing my questions.

Dictated but not reviewed,
Becker

rbs prototype rbi should convert T::Enumerator properly

In Sorbet, T::Enumerator only has one argument (e.g. T::Enumerator[String]), but in ruby-signature it has 2.

Right now, this is the input

class Foo
  sig { returns(T::Enumerator[String]) }
  def bar; end
end

And this is what rbs prototype rbi outputs:

class Foo
  def bar: () -> Enumerator[String]
end

The rbs above causes this error when parsed:

::Enumerator expects parameters [Elem, Return], but given args [::String] (Ruby::Signature::InvalidTypeApplicationError)

I'm not 100% sure, but I think T::Enumerator[String] should be converted to Enumerator[String, self] in ruby-signature?

How can we write `Array#zip` with arbitrary number of arguments?

The current signature for Array#zip accepts exactly one array argument, but the actual zip acceptes one or more arrays. Can we write a signature for this?

pp [1, 2, 3].zip(%w(foo bar baz), [:foo, :bar, :baz])
#=> [[1, "foo", :foo], [2, "bar", :bar], [3, "baz", :baz]]

[docs] whitespace claifications

Dearest Maintainer,

I am wondering how the white space rules work in the rbs file. For example "Class instance type":

Hash[Symbol, String]

Is given as an example in the syntax doc. The ebnf like syntax is

_class-name_ _type-arguments_                (Class instance type)

_class-name_ ::= _namespace_ /[A-Z]\w*/
_type-arguments_ ::=                                    (No application)
                   | `[` _type_ `,` ... `]`             (Type application)

for this example I am wondering about Type application. As written I would expect the syntax to be without a space

Hash[Symbol,String]

I am wondering if white space matters at all? Are these valid as well?

Hash[ Symbol,    String]

Hash [ Symbol,    String]

Hash[ Symbol,(\t i cant figure out how to enter a tab in this editor)String]

Hash[ Symbol,
String]

Reading over the doc some more I would expect this example

def +: (Float) -> Float
     | (Integer) -> Integer
     | (Numeric) -> Numeric

be written as

def +: (Float) -> Float | (Integer) -> Integer  | (Numeric) -> Numeric

Other then making it slightly easier to parse I am uncertain where changing the white space (adding or removing spaces,tab,newlines) would change the outcome of the signature. I thought I would ask instead of assume.

Any guidance would be much appreciated. Thank you for your work and time on this project.

Dictated but not reviewed,
Becker

Deal with method_missing

Will there be a way to deal with overriding method_missing? For instance, if I delegate the method calls to an instance variable, I'd like to "inherit" this object's API:

def method_missing(meth, *args)
  if @obj.respond_to?(meth)
    @obj.__send__(meth,  *args)
  else
    super
  end
end

Syntax for class level attributes/class instance variables & class variables

Hi there, really excited about RBS and am experimenting with steep.

Sorry if I have missed it or the reasoning behind this not being supported but wondering if there is a way to express in RBS the following creating a class instance var:

class Tester
  class << self
    attr_accessor :var # @var is a String type
  end
end

_attribute-member_ doesn't seem to support a self. prefix

Also any syntax for defining the type of class vars (@@var_name) ? For example from https://www.ruby-lang.org/en/documentation/faq/8/

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Use of such a construct is mostly discouraged though so maybe RBS is avoiding support for it?

Thanks!

Can't make extension for Enumerable

An error occurred when I tried to make an extension for Enumerable.

# rbs

extension Enumerable[Elem, Return] (test)
end
$ rbs -I . validate
(snip)
/path/rbs-0.3.1/stdlib/builtin/hash.rbs:108:0...1029:3:    Validating class/module definition: `::Hash`...
/path/rbs-0.3.1/lib/rbs/definition_builder.rb:70:in `map': undefined method `name' for #<RBS::Types::Tuple:0x000055d61d606780 @types=[#<RBS::Types::Variable:0x000055d61d613ae8 @name=:K, @location=nil>, #<RBS::Types::Variable:0x000055d
61d613980 @name=:V, @location=nil>], @location=#<RBS::Location:380 @buffer=/path/rbs-0.3.1/stdlib/builtin/hash.rbs, @pos=3152...3158, source='[K, V]', start_line=109, start_column=21>> (NoMethodError)
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:70:in `block in build_ancestors'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:65:in `each'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:65:in `build_ancestors'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:58:in `block in build_ancestors'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:50:in `each'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:50:in `build_ancestors'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:210:in `block in build_instance'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:811:in `try_cache'
        from /path/rbs-0.3.1/lib/rbs/definition_builder.rb:202:in `build_instance'
        from /path/rbs-0.3.1/lib/rbs/cli.rb:308:in `block in run_validate'
        from /path/rbs-0.3.1/lib/rbs/environment.rb:70:in `block in each_decl'
        from /path/rbs-0.3.1/lib/rbs/environment.rb:69:in `each'
        from /path/rbs-0.3.1/lib/rbs/environment.rb:69:in `each_decl'
        from /path/rbs-0.3.1/lib/rbs/cli.rb:304:in `run_validate'
        from /path/rbs-0.3.1/lib/rbs/cli.rb:85:in `run'
        from /path/rbs-0.3.1/exe/rbs:7:in `<top (required)>'
        from /path2/bin/rbs:23:in `load'
        from /path2/bin/rbs:23:in `<main>'

The error message points Hash.rbs.

include Enumerable[[K, V], Hash[K, V]]

It looks [K, V] is the cause.

Is `:perm` missing in `IO.write` and `Pathname#write`?

I noticed that the IO.write and Pathname#write method definitions do not have a :perm option.

def self.write: (String name, _ToS arg0, ?Integer offset, ?external_encoding: String external_encoding, ?internal_encoding: String internal_encoding, ?encoding: String encoding, ?textmode: untyped textmode, ?binmode: untyped binmode, ?autoclose: untyped autoclose, ?mode: String mode) -> Integer

def write: (String content, ?Integer offset,
# open_args
?mode: Integer | String,
?flags: Integer,
?external_encoding: encoding,
?internal_encoding: encoding,
?encoding: encoding,
?textmode: bool,
?binmode: bool,
?autoclose: bool,
) -> Integer

The related IO.write document says:

:perm
integer
Specifies the perm argument for open().

Then, I have the following questions:

  • Is there some reason for the missing :perm option?

  • Can we unify the common options' definitions? And does it make sense? Like this:

    # IO
    def self.write: (String name, _ToS content, ?Integer offset, ?open_args options) -> Integer
    
    # Pathname
    def write: (String content, ?Integer offset, ?open_args options) -> 
    

See also:

rbs string literals compatibility with frozen strings

Let's say I have the following class:

# foo.rb
class Foo
  BAR = "abc".freeze
end

and an equivalent rbs file:

# foo.rbs
class Foo
  BAR: "abc"
end

If I am using steep and run bundle exec steep check I would get the following error:

IncompatibleAssignment: lhs_type="abc", rhs_type=::String (BAR = "abc".freeze)
  ::String <: "abc"
==> ::String <: "abc" does not hold

I could, however, do the following with no errors:

class Foo
  BAR = "abc"; BAR.freeze
end

Is that just because the return type of String#freeze is just marked as ::String?

(rbs version 0.10.0,
steep version 0.24.0,
ruby version 2.7.1)

Suggestion/discussion about typing with the splat operator (*, **, "rest" or "varargs")

Hello. Lately I have been playing with the type system in Ruby and I notice some patterns that cannot be represented by the RBS syntax... So I wanted to start a discussion about it. I hope we can get some fruitful insights.

(OBS.: The discussion here is based on the example about splatting in the syntax document and one of my doubts in the issue in soutaro/steep#160.)

Motivation

In the syntax doc, it seems that the type of rest arguments can only be uniform (i.e. the same type for all the entries)...
So in the following example it seems that the current grammar does not allow writing <something> in such a way that the arguments given to lazy are type matched against the arguments in the signature or @callable (please correct me if I am wrong)

class Effect
  def initialize(callable)
    @callable = callable
  end

  def lazy(*args)
    -> { @callable.(*args) }
  end
end

module MyModule
  def self.func(a, b)
    a * b
  end
end

# @type var effect: ^() -> Array[String | Integer]
effect = Effect.new(MyModule.method(:func)).lazy([1, "a"] , 2)
puts "Effect: #{effect.()}" # => [1, "a", 1, "a"]

# ------------------- .rbs ---------------------
class Effect[<something>, S]
  def initialize: (^(*<something>) -> S) -> void
  def lazy: (*<something> args) -> (^() -> S)
end

interface _Prod
  def *: (Numeric) -> _Prod
end

module MyModule
  def self.func: (_Prod, Numeric) -> _Prod
end

Proposal

Based on this my proposal would be change the meaning of the type associated with the splat operator to refer to the entire array of arguments instead of each argument individually (i.e. make T in (*T) -> S mean the type of the array instead of the type of each individual element).

Please notice that with this change we can represent both uniform and heterogeneous lists of arguments (if someone wants to enforce uniform types, it can be done with (*Array[T]) -> S). Without the grammar is somehow incompatible with the main use of the splat operator nowadays (heterogeneous types).

Please notice the suggestion is also valid for keyword arguments: T in (**T) -> S would represent the entire type of the hash of keywords, not the type of each keyword value. If someone wants to enforce uniformity in the type, that could be done with `(**Hash[Symbol, T]) -> S.

Summary of the proposal:

nowadays with the proposed change
uniform rest args (*A, **B) -> C (*Array[A], **Hash[Symbol, B]) -> C
heterogeneous rest args --impossible-- (*A, **B) -> C

Other implementations/languages

By the documentation, Sorbet doesn't seem to support heterogeneous rest args either, and apparently this decision was inspired by Scala. However Ruby syntax is different from Scala in that respect (disclaimer: I might be wrong here, I am not a Scala programmer). Apparently, according to this website, Scala prohibits the un-splatting of heterogeneous lists, and instead provides an alternative method to obtain virtually the same effect. This is not the case of Ruby... un-splatting of heterogeneous arrays is super-common (and the standard way we forward args between method calls), and I don't remember now any existing alternative for un-splatting...
Therefore, I believe taking inspiration from Scala is not a good call on this matter.

We can instead look on how our friends in the Python community are dealing with this, since the grammar for splats in Python and Ruby is almost identical. Indeed they also opted by using uniform rest args, however this decision created a series of issues, and difficulties to produce type signatures even inside the standard library, that they are currently struggling to solve. Particularly it is impossible to annotate complex decorators - one of the pillars of Python's high-order functions - that modify/hide/augment the argument list of the decorated functions -- please notice that the decorator pattern in Python is similar to the pattern of wrapping blocks in Ruby/passing blocks around. The following links represent some of the issues pointed out by the Python community and the attempts to solve the limitation (mostly based on providing an alternative construct interpreted by the type checker).

It seems that main contributors of Python's reference type checker do appreciate that heterogeneous rest args are important and are debating for an alternative instrument to represent it, other than directly in the type annotation grammar to avoid breaking retro-compatibility. However, since the type grammar for stubs is Ruby is not consolidated yet, I see this as a great opportunity, so we can adopt a grammar that can handle both use cases (heterogeneous and uniform) and be more future proof.

Optional blocks / nilable procs?

In a lot of Ruby code, you can use a method either with or without a block. For example, with create_table in Rails' ActiveRecord:

# With block
create_table(:suppliers) do |t|
  t.string :name
end

# Without block
create_table(:suppliers)

In Sorbet, the signature for this methods looks like this (simplified to remove a bunch of unnecessary stuff):

sig do
  params(
    table_name: T.any(String, Symbol),
    blk: T.nilable(T.proc.params(t: ActiveRecord::ConnectionAdapters::TableDefinition).void)
  ).void
end
def create_table(table_name, &blk); end

But when I run bundle exec rbs prototype rbi foo.rbi on a file with a nilable proc, I get this error:

Unexpected block type: ^(ActiveRecord::ConnectionAdapters::TableDefinition t) -> void?
(SCOPE@9:2-9:41
 tbl: [:table_name, :blk]
 args:
   (ARGS@9:19-9:35
    pre_num: 1
    pre_init: nil
    opt: nil
    first_post: nil
    post_num: 0
    post_init: nil
    rest: nil
    kw: nil
    kwrest: nil
    block: :blk)
 body: nil)

So, how are we supposed to represent optional blocks in rbs files?

Link to "The State of Ruby 3 Typing" in the README

I arrived at this repository via the Ruby 3.0.0 Preview 1 Released blog post.

The README didn't immediately make it clear to me how this might be integrated into a Ruby developer's workflow. After a bit of Googling I came across this excellent blog post, The State of Ruby 3 Typing , which explains the high level purpose of this gem and how it intends to fit into the ecosystem.

I wonder if you'd consider linking to that blog post in the README to make it easier for future visitors to understand the high level goals of the project?

Or perhaps incorporate some parts of the blog post into an expanded version of the README to give more context to the problem this (excellent!) gem is solving.

Thank you for all your hard work on this project โœจ

bin/sort sorts private as though it were a method

private and protected shouldn't be sorted by bin/sort.

Input:

module Foo
  def baz: (*untyped) -> untyped

  private

  def foo: (*untyped) -> untyped

  def bar: (*untyped) -> untyped
end

Output:

module Foo
  private

  def bar: (*untyped) -> untyped

  def baz: (*untyped) -> untyped

  def foo: (*untyped) -> untyped
end

What I'd expect the output to look like, with the private methods sorted within themselves:

module Foo
  def baz: (*untyped) -> untyped

  private

  def bar: (*untyped) -> untyped

  def foo: (*untyped) -> untyped
end

[test] Better test failure reporting

If you have a test which fails, the stdlib test reports like the following:

Minitest::Assertion: Expected ["[Dir.mktmpdir] ReturnTypeError: expected `::Integer` but returns `\"/var/folders/b9/sw1f2b996cd_tclwhhmh5tfc0000gn/T/d20200108-67558-4ri1a4\"`", "[Dir.mktmpdir] ReturnTypeError: expected `::Integer` but returns `\"/var/folders/b9/sw1f2b996cd_tclwhhmh5tfc0000gn/T/foo20200108-67558-2jmzq0bar\"`", "[Dir.mktmpdir] ReturnTypeError: expected `::Integer` but returns `\"/var/folders/b9/sw1f2b996cd_tclwhhmh5tfc0000gn/T/foo20200108-67558-315pdj\"`", "[Dir.mktmpdir] ReturnTypeError: expected `::Integer` but returns `\"/var/folders/b9/sw1f2b996cd_tclwhhmh5tfc0000gn/T/d20200108-67558-42bd5d\"`"] to be empty.

It tells us there is an inconsistent method call but does not tell which one is the source of the failure.

We need an improvement to help to identify the source of the failure.

Add a shorthand for "TrueClass | FalseClass"

I see in syntax.md there's a bool type, but it allows "nil or any other values", which seems to defeat the purpose a bit.

I'd expect passing anything other than true or false to warn, because otherwise the type signature doesn't seem to have a point?

# rbs file
def foo: (bool) -> String
# ruby file
baz = 'string'

foo(true) #=> good
foo(baz) #=> this should make the typechecker warn?

If changing bool isn't going to happen, I'd like a simple shorthand for TrueClass | FalseClass that would make passing nil or a string to a method expecting a boolean give a warning. Maybe bool!? I don't think ! has any meaning in the signature syntax right now, but it might not be a good idea to use ! if you plan on using it for something else at some point.

# rbs file
def foo: (bool) -> String
def bar: (bool!) -> String
# ruby file
baz = 'string'

foo(true) #=> good
foo(baz) #=> ok
bar(baz) # warning

Thanks :)

Blocks, Procs and Methods

I'm not sure how best to describe this (or whether it should be classed as an issue at all)

I have the following code, which sorts an array of hashes using sort_by:

Steep is happy with the sorting protocol being provided as a block, e.g.

  my_array.sort_by { |x| x.slice(:country, :lastname, :id).values }

Steep is also happy if I replace the block by a lambda:

  by_country = ->(x) { x.slice(:country, :lastname, :id).values }
  my_array.sort_by(&by_country)

But if I define the sorting protocol as a method:

  def by_country(person)
    person.slice(:country, :lastname, :id).values
  end
  
  my_array.sort_by(&method(:by_country))

then I get the following warning/error:
UnresolvedOverloading: receiver=::Array[::Hash[Symbol, untyped]], method_name=sort_by, method_types=() { (::Hash[Symbol, untyped]) -> (::Array[untyped] | ::Comparable) } -> ::Array[::Hash[Symbol, untyped]] | () -> ::Enumerator[::Hash[Symbol, untyped], ::Array[::Hash[Symbol, untyped]]] (data.sort_by(&method(:by_country)))

Leaving aside a question of coding style, I would expect all three approaches to parse in the same way. Any suggestions?

Some quick way of declarinng common signature, such as () -> void

There are some "state management" methods, which do not have relevant type information. Ex: a #close method on some object.

A proposal for such a syntax would be to group multiple methods for such a declaration (it could also work for other signatures):

def close, trigger, contact: () ->  void

NameError: uninitialized constant RUBY_27_OR_LATER

Problems

Currently, NameError is raised when bundle exec ruby test/stdlib/String_test.rb is executed.

Log

~/ghq/github.com/user_name/ruby-signature %bundle exec ruby test/stdlib/String_test.rb                                  add-test-on-gc
Started with run options --seed 8486

ERROR["test_aref", #<Minitest::Reporters::Suite:0x0000557a4dff03d0 @name="StringInstanceTest">, 0.16184525199969357]
 test_aref#StringInstanceTest (0.16s)
Minitest::UnexpectedError:         NameError: uninitialized constant RUBY_27_OR_LATER
        Did you mean?  RUBY_PLATFORM
            test/stdlib/String_test.rb:141:in `test_aref'

  128/128: [=================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.23136s
128 tests, 1752 assertions, 0 failures, 1 errors, 0 skips

Runtime type-check breaks with usage of curry

I think that the usage of Proc.curry is not compatibly with the alias method chain instrumentation way, that is the basis of the current runtime checks.

I have this signature:

def on_object: (Object, Object) -> void

When I call on_object(1, 2), all good. However, when I do:

method(:on_object).curry[1]

#  TypeError: [MyObject#on_object] ArgumentError: expected method type (::Object, ::Object) -> void

I've tried updating this locally to come up with a fix, but I don't see any info in the callstack that tells me the method's been called through currying. This might be a ruby limitation, I guess?

[Request] rbs prototype should leverage yard comments

rbs prototype rb command is useful to boostrap a rbs file. It would be convenient to have yard comment parsed and used when generating signature.

For instance, taking this example from the yard documentation:

# Converts the object into textual markup given a specific format.
#
# @param format [Symbol] the format type, `:text` or `:html`
# @return [String] the object converted into the expected format.
def to_format(format)
  # format the object
end

rbs prototype rb current returns:

class Object
  # Converts the object into textual markup given a specific format.
  #
  # @param format [Symbol] the format type, `:text` or `:html`
  # @return [String] the object converted into the expected format.
  def to_format: (untyped format) -> nil
end

whereas it could return something richer similar to:

class Object
  # Converts the object into textual markup given a specific format.
  #
  # @param format [Symbol] the format type, `:text` or `:html`
  # @return [String] the object converted into the expected format.
  def to_format: (Symbol format) -> String
end

The reasoning behind this feature would be to allow some users to maintain signature within ruby file using an existing and known syntax (yard) and get the benefit from rbs.

How to deal with dynamic classes

As one core meta-programming feature from ruby is enhancing of existing classes, or even the creation of anonymous new ones, I'm wondering whether this is possible already. Something like:

module Ext
  def foo;end
end

class A
  def self.enhance
    Class.new(self) do
      include Ext
    end
  end
end

How could one type the result of calling .enhance?

Keyword as argument name is syntax error

Problem

Currently, RBS does not allow a keyword as an argument name.

class C
  # "type" is a keyword in RBS, and a keyword is not allowed at here (var_name_opt).
  # So it is syntax error.
  def foo: (untyped type) -> void

  # It is also syntax error.
  def foo: (type: untyped type) -> void

  # It is not a syntax error.
  def foo: (type: untyped) -> void
end

I found this problem while #153 .
We have the following Ruby code, and rbs prototype rb command generates a code with synax error from it.

class C
  def foo(type:)
  end
end

Solution

I have several ideas to fix it.

  • Allow keyword as an argument name.
    • diff --git a/lib/ruby/signature/parser.y b/lib/ruby/signature/parser.y
      index 2c8475e..654b232 100644
      --- a/lib/ruby/signature/parser.y
      +++ b/lib/ruby/signature/parser.y
      @@ -941,7 +941,7 @@ rule
             }
       
         var_name_opt:
      -    | tLIDENT | tINTERFACEIDENT
      +    | tLIDENT | tINTERFACEIDENT | identifier_keywords
       
         qualified_name:
             namespace simple_name {
    • It works well and does not increase conflicts.
    • But I'm wondering if it conflicts in the future.
  • Allow escaping for argument name, like method name.
    • def foo: (untyped `type`) -> void
    • I like this idea.
  • Fix rbs prototype rb command to avoid generating syntax error code.
    • e.g. replacing type with _type
    • It works for rbs prototype rb, but I guess we'd like to use type as argument name.
    • So I think it is not a good idea.

What do you think about these solution ideas?

rbs prototype rbi misses comments for multiline sigs

In Sorbet you can have multiline sigs using do/end blocks, like so:

module Bar
  # This is a method.
  sig do
    params(x: Integer).void
  end
  def self.foo(x); end
end

When you use rbs prototype rbi on this signature to convert it to RBS, the comment doesn't get included in the RBS output:

module Bar
  def self.foo: (Integer x) -> void
end

I wrote a test to show this issue, if you want to use it:

  def test_comment_with_multiline_sig
    parser = RBI.new

    parser.parse <<-EOF
module Bar
  # This is a method.
  sig do
    params(x: Integer).void
  end
  def self.foo(x); end
end
    EOF

    assert_write parser.decls, <<-EOF
module Bar
  # This is a method.
  def self.foo: (Integer x) -> void
end
    EOF
  end

Supporting generic type aliases?

Currently, type can not have be generic.

This would be quite useful, in particular to define array[T]. Is it something that is planned?

type array[T] = Array[T] | _ToAry[T]

[docs] questions about syntax.md

Dearest Maintainer,

I hope it is an ok day. I was reading over https://github.com/ruby/rbs/blob/master/docs/syntax.md and I was unsure a few things.

  1. What format is the syntax documented in? It kind of looks like EBNF but I cant really parse it.

  2. It is meant to be complete? I ask because i see symbol-literal for example but it is not defined on this page. There are a few of these.

  3. Is this a generated page from a script?

Inquiring minds want to know. Thank you for your time.

Becker

[test] Test coverage

There is no way to measure how well the tests cover the method call variations.

Assume we have a method definition like:

def foo: (Integer | String) -> (Symbol | Array[String])
       | (Integer, String) -> void

We may write a test for the method like:

def test_foo
  foo(1)
  foo(1, "3)
end

It covers part of the first overload and second overload, but we need more cases to cover all of the variations.

def test_foo
  foo(1)              # Assume this returns Symbol
  foo("foo")          # Assume this returns Symbol
  foo(3)              # Assume this returns Array[String]
  foo(1, "3)
end

So, having a tool to measure the coverage would help writing tests.

rbs prototype rb can have duplicated declarations

Problem

rbs prototype rb command may generate duplicated declarations.
For example:

a.rb

class A
  def foo() end
end

class A
  class B
    def bar() end
  end
end
$ rbs prototype rb c.rb
class A
  def foo: () -> untyped
end

class A
end

class A::B
  def bar: () -> untyped
end

In this case, class A is duplicated.

And it easily causes in the real applications.

Solution

Now I avoid the problem with the following code.
full version: https://gist.github.com/pocke/a3faa4b87416c3d899e626f9197f2174#file-steep-rake-L13-L33

decls = Ruby::Signature::Parser.parse_signature(sig_base_path.read)
decl_map = {}
decls.each do |decl|
  if d = decl_map[decl.name]
    # TODO: Is it right for decl is not a class / module?
    c = Ruby::Signature::AST::Declarations::Class
    m = Ruby::Signature::AST::Declarations::Module
    next unless d.is_a?(c) || d.is_a?(m)
    next unless decl.is_a?(c) || decl.is_a?(m)

    d.members.concat decl.members
    if d.is_a?(c) && decl.is_a?(c)
      decl_map[decl.name] = c.new(name: d.name, type_params: d.type_params, super_class: d.super_class || decl.super_class, members: d.members, annotations: d.annotations, location: d.location, comment: d.comment)
    end
  else
    decl_map[decl.name] = decl
  end
end

But I think rbs command should provide a solution(s) for the problem.

So, I think it is better if rbs prototype rb command displays deduped declarations.

And we can implement rbs dedupe sub-command as an alternative. The sub-command may be useful for RBS files that are generated by other programs. But I'm not sure the sub-command is actually necessary.

By the way, I guess rbs prototype rbi also has the same issue.

How about the ideas? I'd like to implement them if they are acceptable.

Reader/Writer/Rewindable interfaces

There are a couple of agreed-upon interfaces in stream scenarios, which have been supported by ruby since always, and might break with the recent typing.

For instance, IO.select works with any object that responds to #.to_io, not with IO objects, like the typing info suggests. Likewise, IO.copy_stream works if the first argument responds to read(size, buffer), and the second responds to write(data) -> size, and not only with IO objects. Both usages will break under typing if these typing declarations are to be kept.

On the former, I suspect that usage of OpenSSL::SSLSocket in IO.select will break if under typing.

Alternatively, I'd like to suggest the introduction of _Reader, _Writer, _Rewindable, and _ToIO interfaces, which can be used in these functions, but also in others where the same criteria applies.

I've been using and supporting such APIs for a long time and they are very convenient, and have also written about it.

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.