TypeProf is Ruby's official type inference engine that automatically generates RBS type signatures by analyzing your code. Developed by the Ruby core team and shipping with Ruby 3.0+, TypeProf eliminates the tedious work of writing type signatures by hand, letting you bootstrap type coverage for entire codebases in minutes.
What makes TypeProf compelling is its ability to understand Ruby's dynamic nature while producing static type information. Unlike tools that require you to annotate every method, TypeProf examines how values flow through your program and infers types automatically. You write normal Ruby code, run TypeProf, and get RBS signatures that document your interfaces.
This guide shows you how to use TypeProf effectively, understand what it can and can't infer, and leverage it to build comprehensive type coverage for your Ruby projects.
Prerequisites
You'll need Ruby 3.0 or later since TypeProf ships as part of the standard library starting from that version. This guide assumes you're comfortable with Ruby fundamentals and have a basic understanding of types. Familiarity with RBS syntax helps but isn't required - TypeProf generates the signatures for you.
Setting up your first TypeProf project
Let's create a Ruby project to explore how TypeProf analyzes code and generates signatures. Start by setting up a basic structure:
Open the generated Gemfile and add TypeProf:
Install it:
Create a directory for the source code:
Then create a simple Ruby file to analyze:
Run TypeProf on this file:
TypeProf analyzed the code and inferred that @name is a String based on how it's used in string interpolation. It also determined that initialize takes a String parameter and returns void, while the greeting methods return String values.
The inference works by tracking how values flow through your program. When TypeProf sees "Hello, #{@name}!", it knows the result is a string, and therefore greet returns a string. When it sees @name = name in initialize, it infers that name must be a string to make the string interpolation valid.
Create a file that uses the Greeter class:
Run TypeProf on both files:
The output remains the same because the usage in app.rb confirms TypeProf's inferences. The concrete value "Alice" validates that initialize expects a String.
This is TypeProf's basic workflow: you point it at Ruby files, it analyzes them, and it generates RBS signatures. The more context you provide through actual usage, the more confident TypeProf becomes about the types.
How TypeProf infers types
TypeProf uses abstract interpretation to track how values move through your program. It doesn't execute your code - instead, it simulates execution symbolically, building up knowledge about what types exist at each point.
Let's see this in action with a method that performs calculations:
Run TypeProf:
TypeProf inferred that these methods work with integers, but how? It saw total = 0 which is an integer literal, then tracked that total gets passed to add along with elements from the numbers array. Since total is an integer, the array elements must also be integers for add to work consistently.
The inference becomes more interesting when you give TypeProf concrete usage examples. Create a file that calls these methods:
Run TypeProf on both files:
The signatures match because the usage confirms the inferred types. TypeProf saw you passing integer literals and arrays of integers, which validates its analysis.
Now let's see what happens with methods that work with multiple types:
Run TypeProf:
TypeProf falls back to untyped for the parameter because it can't determine from the code alone what types will actually be passed. The method handles multiple types differently, but without concrete usage, TypeProf stays conservative.
Provide usage examples to help TypeProf:
Run TypeProf on both files:
Now TypeProf inferred a union type based on the actual usage patterns. It saw three different types being passed and generated a signature that accepts any of them.
This demonstrates an important principle: TypeProf's accuracy improves when you provide representative usage. The more your codebase shows how methods get called, the better TypeProf understands the intended types.
Understanding TypeProf's limitations
TypeProf works remarkably well for straightforward Ruby code, but it has boundaries. Understanding where it struggles helps you know when to write signatures manually or refactor code for better inference.
Dynamic method definitions confuse TypeProf because it analyzes code statically. Here's an example:
Run TypeProf:
TypeProf doesn't generate signatures for the dynamically defined methods because it can't determine at analysis time what methods will exist. The define_method call happens too dynamically for static analysis to follow.
For code like this, write the RBS signature manually:
Methods that use method_missing face similar issues:
Run TypeProf:
TypeProf generates signatures for the actual methods defined, but it can't infer what methods method_missing will handle. You'll need to document those separately in RBS.
TypeProf also struggles with complex control flow and conditional logic:
Run TypeProf:
TypeProf inferred the return type is (Integer | String)? because the case statement might return nil if mode doesn't match any branch. It also knows that when the mode matches :double or :triple, an integer returns, but when mode is :stringify, a string returns.
The nullable return (? at the end) happens because TypeProf recognizes the case statement might not match anything. Fix this by adding an else clause:
Run TypeProf again:
Now the return type is no longer nullable because TypeProf knows the else branch ensures something always returns.
External dependencies also limit TypeProf's effectiveness. When your code calls methods from gems, TypeProf needs RBS signatures for those gems to understand the types:
Run TypeProf:
TypeProf successfully inferred the signature because Ruby's standard library includes RBS signatures. If you use gems without RBS signatures, TypeProf will generate untyped for those interactions.
The key is recognizing when TypeProf's output needs refinement. Use it as a starting point, then review and improve the generated signatures based on your actual requirements.
Final thoughts
This article showed you how TypeProf automatically generates RBS type signatures by analyzing Ruby code. The tool examines how values flow through your program and infers types without requiring annotations, making it practical to bootstrap type coverage quickly.
TypeProf works best with straightforward Ruby code where type flow is clear. Provide concrete usage examples to improve accuracy, and recognize when dynamic features like metaprogramming require manual RBS signatures. The combination of automated generation and targeted refinement creates comprehensive type documentation efficiently.
As Ruby's official type inference tool, TypeProf represents a low-friction path to static type checking. Generate signatures quickly, review them for accuracy, and maintain them alongside your code as it evolves.