When adding RBS signatures to an existing Ruby library, how do rbs prototype rb and TypeProf differ in the signatures they generate?
answer
- prototype reads syntax only
- mostly untyped placeholders
- TypeProf: abstract interpretation
- types come from observed calls
- -o sig/app.gen.rbs, then edit
basics
~20 srbs prototype rb reads only the syntax, so it emits every class, method and instance variable with mostly untyped types. TypeProf abstractly runs the code and infers real types from the calls it sees; both outputs are drafts to edit.
solid answer
~50 s`rbs prototype rb lib/billing/invoice.rb` parses the file and prints a skeleton: every class, `def`, instance variable and constant, with parameters as `untyped` except literal defaults (`currency: :usd` becomes `?currency: ::Symbol`) and only obvious returns typed (a string literal becomes `::String`). It never runs anything, so it is fast and complete but says little. **TypeProf** is a type-level Ruby interpreter: `typeprof sig/billing.rbs lib/billing.rb -o sig/billing.gen.rbs` abstractly executes the code, replacing values by their classes, follows calls into methods and collects what flows in and out, so `invoice.add_line("Setup", 5000)` yields `(String, Integer)`. Its answers are only as good as the calls it sees: a parameter nobody calls stays `untyped`, and unions widen when callers disagree. `rbs prototype rbi` converts Sorbet RBI files; `prototype runtime` is deprecated since RBS 4.1. Treat every generated file as a first draft.
code
bash · 4 linesrbs prototype rb lib/billing/invoice.rb > sig/billing/invoice.rbs
typeprof sig/billing/invoice.rbs lib/billing/invoice.rb test/invoice_test.rb \
-o sig/billing/invoice.gen.rbs
rbs -I sig validatego deeper
Recall that rbs prototype rb generates a skeleton of untyped signatures and TypeProf infers types by analysing the code.
Explain the mechanisms: prototype parses syntax only, TypeProf abstractly executes code and records observed argument and return types.
Show the workflow: prototype for shape, TypeProf fed with tests for types, hand tightening, then validate and check, plus what metaprogramming hides.
Weigh generated signatures against authored ones: generation seeds coverage quickly, but signatures only add value once someone owns their intent.
## The problem both tools solve Writing RBS by hand for an existing billing library of fifty classes is slow. Two tools produce a **first draft** of the signatures, and they work in opposite ways: one reads the code as text, the other runs it at the level of types. ## `rbs prototype rb`: a syntax skeleton `rbs prototype rb` parses Ruby files and prints the declarations it finds: - every `class` and `module`, with superclasses and mixins it can see; - every `def`, with parameter kinds preserved (optional, keyword, rest, block); - instance variables assigned in the code and constants; - types that are obvious from syntax: a literal default value (`currency: :usd` becomes `?currency: ::Symbol`) and a method whose body ends in a string literal returning `::String`. Everything else is **`untyped`**. The output is complete in shape and almost empty in content, and it has blind spots by design: methods created by `define_method` or other metaprogramming are invisible to a parser. The other formats of the same command: | Command | Reads | Status | |---|---|---| | `rbs prototype rb` | Ruby source | current | | `rbs prototype rbi` | Sorbet `.rbi` files | current | | `rbs prototype runtime` | loaded classes via reflection | deprecated since RBS 4.1 | ## TypeProf: inference by abstract interpretation **TypeProf** calls itself a type-level Ruby interpreter. Instead of running `add_line("Setup", 5000)` with those values, it runs it with the *types* `String` and `Integer`, follows the call into the method body, calls core methods through their RBS signatures, and records: 1. the argument types each method received, 2. what it returned, 3. what was assigned to each instance variable, aggregated per class. The result is printed as RBS. A typical run passes your hand-written signatures first so TypeProf can build on them: ```bash typeprof sig/billing.rbs lib/billing.rb -o sig/billing.gen.rbs ``` ## What each gets wrong - **Prototype** is never wrong about shape but gives you almost no types. - **TypeProf** gives real types but only from **observed flows**: a parameter that no analysed code passes stays `untyped`, a method called with an `Integer` in one place and a `Float` in another becomes `Integer | Float` even if you meant `Numeric`, and dynamic code (`send`, `method_missing`) defeats it. - Neither knows your intent: whether `find_line` should promise `LineItem?` or raise, or whether a Hash is really a record. ## A practical workflow 1. Run `rbs prototype rb` over `lib/` to get the full skeleton and commit it. 2. Run TypeProf with an entry script or the test files as extra input so it sees realistic calls, and copy the useful types across. 3. Tighten by hand: replace `untyped`, add `?` where nil is possible, introduce interfaces and type aliases. 4. Validate with `rbs -I sig validate`, then let a checker such as Steep find where the code and signatures disagree. ## Versions and the editor TypeProf 0.33 requires Ruby 3.3 or later; it also runs as a language server, and `typeprof --init` writes a `typeprof.conf.jsonc` for its editor extension. Ruby 4.0.7 bundles older copies (rbs 3.10.0, typeprof 0.31.1), so a project pins the versions it wants in its Gemfile.
- Why does TypeProf leave some parameters `untyped` even for code with good tests?TypeProf learns a parameter's type only from calls it analyses. If the files you pass never call the method, or call it only through `send` or metaprogramming it cannot follow, nothing flows into the parameter and it stays `untyped`. Passing the test files or an entry script as extra inputs gives it more call sites.
- You are migrating from Sorbet. Which prototype format helps?`rbs prototype rbi` reads Sorbet `.rbi` files and emits the equivalent RBS declarations, so existing Sorbet signatures become a starting point instead of being retyped by hand.
saying these in an interview costs you the question
- rbs prototype rb runs the code to discover argument types.
- TypeProf's output is a finished, verified signature you can ship unchanged.
- TypeProf infers concrete parameter types even for methods nothing calls.
- rbs prototype runtime is the recommended generator in current RBS.
- TypeProf checks the code against signatures the way Steep does.