Don't understand how to write and setup ruby 3 RBS type checking.
Let's say I have a similar file:
### file name: my_project/book.rb
class Book
def initialize(pages)
@pages = pages
end
def pages
@pages
end
end
first_book = Book.new(100)
puts first_book.pages
The question: What should a .rbs
file look like to my file example?
And what CLI command should I run to test it ?
For example $rbs my_project *
or something like this ?
Don't understand how to write and setup ruby 3 RBS type checking.
You are confusing three completely different things here:
RBS is #1, a Type Language. It is exactly what it sounds like: a Language for writing down Types.
It says nothing about what those types mean (that would be a Type System), and it says nothing about whether or not a program is well-typed with respect to a type system (that would be a Type Checker).
RBS was developed based on the observation that there are multiple type checkers in the community already. E.g. almost every Ruby IDE contains a type checker to be able to provide better code completion and "red squigglies". There is Sorbet. There is Steep. There are a couple of others.
There are multiple incompatible type languages already in the Ruby community. The Ruby core library and standard library RDoc comments sometimes use an informal one. YARD has type annotations, but no type language. (They only give an example of what a type language could look like.) Some people write YARD type annotation using the example type language, so use a homegrown or modified one. Then there is RBI, used by Sorbet.
The idea behind RBS is to provide one Type Language as part of the Ruby Language with one API for querying that language. This will allow on the one hand interoperability between existing type checkers and on the other hand more competition. (At the moment, there is a lock-in effect: every type checker uses a different type language, so you can't easily exchange them without rewriting your type signatures from scratch in a different type language. If they use a common type language, you can easily swap them out and choose the best one for your use case.)
The second half of the RBS project is to ship a complete set of RBS signatures for the whole Ruby core library and standard library. That is another problem with the current state of affairs: every type checker has to do this over and over and over again, they all have to write type signatures for the Ruby core and standard libraries in their own type language from scratch. With RBS, there is going to be one standard set of type signatures for the Ruby core and standard libraries.
Also, writing signatures for projects like Rails becomes much easier because you only have to write one set of signatures and you know that it will work with every type checker, every IDE, every linter, every static analyzer, etc.
The question: What should a
.rbs
file look like to my file example?
That depends: what is it that you want to say?
class Book[T]
@pages: T
def initialize: (pages: T) -> void
def pages: () -> T
end
Is one possible type definition.
class Book[T]
attr_reader pages: T
def initialize: (pages: T) -> void
end
might be a slightly better one that captures your intent better than the first one. (Since I don't know your intent but can only see your code, I can't tell which is better.)
class Book
attr_reader pages: 100
def initialize: (pages: 100) -> void
end
Is another possible type definition. This is the strictest possible type signature that will still allow your sample code to run.
class Book
attr_reader pages: Numeric
def initialize: (pages: Numeric) -> void
end
Is also possible, as is
class Book
attr_reader pages: Integer
def initialize: (pages: Integer) -> void
end
And of course, this is also always valid, albeit useless since it doesn't tell us anything that we don't already know:
class Book
attr_reader pages: untyped
def initialize: (pages: untyped) -> untyped
end
Technically speaking, both of the methods in your code take an optional block, whereas we have disallowed that here. To match the semantics of your code exactly, the signature should probably be this:
class Book[T]
@pages: T
def initialize: (pages: T) ?{ (*args: void) -> void } -> void
def pages: () ?{ (*args: void) -> void } -> T
end
This last one is the one that most closely matches what your code currently does and allows. Whether this also accurately captures what you want your code to do and allow, that is a completely different question which only you can answer.
And what CLI command should I run to test it ?
It is not quite clear what mean by "test it". If you mean "type check it", then as I said before: RBS is not a type checker, it is simply a language for writing types, it doesn't know and doesn't care what you do with those types.
RBS does have a test mode, however, but that does something different: it can look at your running code and see whether your code violates any of the type constraints at runtime. However, this is not static type checking.
For example, if you have this signature:
class Foo
def bar: () -> Integer
def quux: () -> Integer
end
and this code:
class Foo
def bar; 23 end
def quux; 'fourty-two' end
end
foo = Foo.new
foo.bar
It will not complain, because it never observed Foo#quux
violating the type constraint at runtime.
And if you have this definition of Foo#quux
instead:
def quux; if rand < 0.5 then 42 else 'fourty-two' end end
Then it will sometimes complain and sometimes not, provided that you actually call quux
somewhere.
So, whether or not you catch any problems depends on your test coverage. If you have a method that returns the wrong type when called with a particular combination of arguments, but you never call it with this particular combination of arguments in your tests, then you will never notice.
Note that this is not so much testing the correctness of your code, it is more for testing the correctness of your type signatures.
For example
$rbs my_project *
or something like this ?
The tools that ship with RBS are primarily designed as libraries, because it is expected that they are going to be integrated in documentation tools (to show type signatures in documentation), test harnesses (to test your type signatures), IDEs (for code completion), or in type checkers.
For example, the testing tool I mentioned above is intended to be used like this:
RBS_TEST_TARGET='Foo::*' bundle exec ruby -r rbs/test/setup test/foo_test.rb
What rbs/test/setup
will do, is very stupidly just ask for Foo.constants.grep(Module)
, then for each of those modules do mod.instance_methods(false)
and literally replace each method with a wrapper that checks the types at method entry and exit and calls the original method. (Using the same technique that ActiveSupport's alias_method_chain
uses.)
As you can see, this is not very command line friendly, it is intended to be integrated into the test harness as a library.
There are a couple of command line tools available:
rbs ast
prints out an AST of the current environment in JSON formatrbs list
prints out a list of the types in the current environmentrbs ancestors
prints out the ancestors of a modulerbs methods
prints out the methods of a modulerbs method
prints out a methodrbs validate
validates the syntax of RBS files and does some basic semantic sanity checksrbs constant
performs constant lookuprbs paths
prints out the paths where RBS looks for signature filesrbs prototype
can generate a skeleton type signature to get you started, it can do this either from your code or from an already existing RBI type signature. (RBI is the type language used by the Sorbet type checker.)rbs vendor
vendors signature files into the project directoryrbs parse
parses an RBS file and prints syntax errorsrbs test
runs your tests with the above-mentioned test hooks injectedHowever, note that this CLI is not intended as the primary interface to RBS. The README explicitly says:
The gem ships with the
rbs
command line tool to demonstrate what it can do and help develop RBS.
The CLI is meant as an example for how to use the API, not as the primary entry point into the API.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With