Using with Steep
Steep is a static type checker for Ruby that uses RBS for type signatures. T-Ruby integrates seamlessly with Steep, allowing you to leverage additional type checking beyond what T-Ruby's compiler provides.
Why Use Steep with T-Ruby?
While T-Ruby performs type checking during compilation, Steep provides:
- Additional validation of generated Ruby code
- Type checking for dependencies and libraries
- IDE integration through Ruby LSP
- Verification that compiled code matches RBS signatures
- Standard Ruby tooling compatibility
Installation
Add Steep to your project:
gem install steep
Or in your Gemfile:
group :development do
gem "steep"
gem "t-ruby"
end
Then:
bundle install
Basic Setup
Step 1: Compile T-Ruby Code
First, compile your T-Ruby code to generate Ruby and RBS files:
trc compile src/
This creates:
build/ # Compiled Ruby files
sig/ # RBS type signatures
Step 2: Create Steepfile
Create a Steepfile to configure Steep:
target :app do
# Check compiled Ruby files
check "build"
# Use T-Ruby generated signatures
signature "sig"
# Use Ruby standard library types
library "pathname"
end
Step 3: Run Steep
steep check
Steep verifies that your compiled Ruby code matches the generated RBS signatures.
Complete Example
Let's walk through a complete example.
T-Ruby source (src/user.trb):
class User
@id: Integer
@name: String
@email: String
def initialize(id: Integer, name: String, email: String): void
@id = id
@name = name
@email = email
end
def greet: String
"Hello, I'm #{@name}!"
end
def self.find(id: Integer): User | nil
# Database lookup would go here
nil
end
end
# Using the class
user = User.new(1, "Alice", "alice@example.com")
puts user.greet
# This would be a type error
# user = User.new("not a number", "Bob", "bob@example.com")
Compile:
trc compile src/
Configure Steep (Steepfile):
target :app do
check "build"
signature "sig"
end
Run Steep:
steep check
Output:
# Type checking files:
build/user.rb:19:8: [error] Type mismatch:
expected: Integer
actual: String
# Typecheck result: FAILURE
Steepfile Configuration
Basic Structure
target :app do
check "path/to/ruby/files"
signature "path/to/rbs/files"
end
Multiple Targets
For larger projects, use multiple targets:
# Application code
target :app do
check "build/app"
signature "sig/app"
library "pathname", "logger"
end
# Tests
target :test do
check "build/test"
signature "sig/test", "sig/app"
library "pathname", "logger", "minitest"
end
Libraries
Include RBS for Ruby standard library and gems:
target :app do
check "build"
signature "sig"
# Standard library
library "pathname"
library "json"
library "net-http"
# Gems with RBS support
library "activerecord"
library "activesupport"
end
Type Resolution
Configure how Steep resolves types:
target :app do
check "build"
signature "sig"
# Ignore specific files
ignore "build/vendor/**/*.rb"
# Configure type checking strictness
configure_code_diagnostics do |hash|
hash[D::UnresolvedOverloading] = :information
hash[D::FallbackAny] = :warning
end
end
Integration with T-Ruby Workflow
Development Workflow
# 1. Write T-Ruby code
vim src/user.trb
# 2. Compile with T-Ruby (catches T-Ruby type errors)
trc compile src/
# 3. Check with Steep (validates Ruby output)
steep check
# 4. Run the code
ruby build/user.rb
Watch Mode
Use both T-Ruby watch and Steep watch together:
Terminal 1 - T-Ruby watch:
trc watch src/ --clear
Terminal 2 - Steep watch:
steep watch --code=build --signature=sig
Now both will automatically recheck as you edit files.
Single Command Workflow
Create a script to run both:
#!/bin/bash
set -e
echo "Compiling T-Ruby..."
trc compile src/
echo "Running Steep..."
steep check
echo "Type checking passed!"
chmod +x bin/typecheck
./bin/typecheck
Advanced Configuration
Strict Mode
Enable strict type checking in Steep:
target :app do
check "build"
signature "sig"
# Strict mode - fail on any type issues
configure_code_diagnostics do |hash|
hash[D::FallbackAny] = :error
hash[D::UnresolvedOverloading] = :error
hash[D::UnexpectedBlockGiven] = :error
hash[D::IncompatibleMethodTypeAnnotation] = :error
end
end
Custom Type Directories
If you have hand-written RBS alongside T-Ruby generated ones:
target :app do
check "build"
# T-Ruby generated signatures
signature "sig/generated"
# Hand-written signatures
signature "sig/manual"
# Vendor signatures
signature "sig/vendor"
end
Rails Integration
For Rails projects:
target :app do
check "app"
signature "sig"
# Rails core libraries
library "pathname"
library "logger"
# Rails gems
library "activerecord"
library "actionpack"
library "activesupport"
library "actionview"
# Configure Rails paths
repo_path "vendor/rbs-rails"
end
# Configure for Rails autoloading
configure_code_diagnostics do |hash|
# Rails uses autoloading - be lenient with constants
hash[D::UnknownConstant] = :hint
end
Ignore Patterns
Ignore generated or vendor code:
target :app do
check "build"
signature "sig"
# Ignore specific patterns
ignore "build/vendor/**/*.rb"
ignore "build/generated/**/*.rb"
ignore "build/**/*_pb.rb" # Protocol buffer generated files
end
Diagnostic Configuration
Customize Steep's diagnostic levels:
target :app do
check "build"
signature "sig"
configure_code_diagnostics do |hash|
# Errors
hash[D::UnresolvedOverloading] = :error
hash[D::FallbackAny] = :error
# Warnings
hash[D::UnexpectedBlockGiven] = :warning
hash[D::IncompatibleAssignment] = :warning
# Information
hash[D::UnknownConstant] = :information
# Hints (lowest severity)
hash[D::UnsatisfiedConstraints] = :hint
# Disable specific diagnostics
hash[D::UnexpectedJumpValue] = nil
end
end
Common Diagnostics
UnresolvedOverloading
Multiple method overloads, Steep can't determine which one:
# In RBS
def process: (String) -> Integer
| (Integer) -> String
# Steep may report UnresolvedOverloading
result = process(input) # Type of input unclear
Fix: Add type annotation or make types clearer.
FallbackAny
Steep can't infer a type and falls back to Any:
result = some_method() # Type unknown, falls back to Any
Fix: Add explicit types in T-Ruby source.
IncompatibleAssignment
Type mismatch in assignment:
x: Integer = "string" # Error: incompatible types
Fix: Correct the type in T-Ruby source.
CI/CD Integration
GitHub Actions
name: Type Check
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Install T-Ruby
run: gem install t-ruby
- name: Compile T-Ruby
run: trc compile src/
- name: Type Check with Steep
run: bundle exec steep check
GitLab CI
typecheck:
image: ruby:3.2
before_script:
- gem install t-ruby
- bundle install
script:
- trc compile src/
- bundle exec steep check
Pre-commit Hook
#!/bin/sh
echo "Type checking with T-Ruby and Steep..."
# Compile T-Ruby
trc compile src/ || exit 1
# Check with Steep
steep check || exit 1
echo "Type check passed!"
Steep Commands
Check
Type check your code:
steep check
With specific target:
steep check --target=app
Watch
Watch for changes and recheck:
steep watch
Specify paths:
steep watch --code=build --signature=sig
Stats
Show type checking statistics:
steep stats
Output:
Target: app
Files: 25
Methods: 147
Classes: 18
Modules: 5
Type errors: 0
Warnings: 3
Validate
Validate Steepfile:
steep validate
Version
Show Steep version:
steep version
Troubleshooting
Steep Can't Find RBS Files
Problem: Steep reports missing type signatures.
Solution: Verify RBS files exist and Steepfile paths are correct:
# Check RBS files were generated
ls -la sig/
# Verify Steepfile paths
cat Steepfile
Type Mismatches
Problem: Steep reports type errors in generated code.
Solution:
-
Check T-Ruby compilation:
trc check src/ -
View generated RBS:
cat sig/user.rbs -
Ensure types match:
trc compile --trace src/
Library Not Found
Problem: Steep can't find library types.
Solution: Install RBS collection or add library to Steepfile:
# Initialize RBS collection
rbs collection init
# Install dependencies
rbs collection install
# In Steepfile
target :app do
signature "sig"
library "pathname", "json"
end
Performance Issues
Problem: Steep is slow on large projects.
Solution:
-
Use multiple targets:
target :core do
check "build/core"
signature "sig/core"
end
target :plugins do
check "build/plugins"
signature "sig/plugins"
end -
Ignore unnecessary files:
target :app do
check "build"
ignore "build/vendor/**"
end
Best Practices
1. Run Steep in CI
Always run Steep in CI to catch type errors:
- name: Type Check
run: |
trc compile src/
steep check
2. Use Steep Watch During Development
Keep Steep running to get immediate feedback:
steep watch --code=build --signature=sig
3. Configure Diagnostics Appropriately
Start permissive, increase strictness over time:
# Start here
configure_code_diagnostics do |hash|
hash[D::FallbackAny] = :warning
end
# Move to this
configure_code_diagnostics do |hash|
hash[D::FallbackAny] = :error
end
4. Keep RBS Files in Version Control
Commit generated RBS files:
git add sig/
git commit -m "Update RBS signatures"
5. Use Both T-Ruby and Steep Checks
T-Ruby catches issues at compile time, Steep validates runtime behavior:
trc check src/ # Compile-time check
trc compile src/ # Generate Ruby + RBS
steep check # Runtime behavior check
Steep vs T-Ruby Type Checking
| Aspect | T-Ruby | Steep |
|---|---|---|
| When | Compile time | After compilation |
| What | .trb files | .rb and .rbs files |
| Purpose | Type safety in T-Ruby code | Validate Ruby output |
| Errors | Prevents compilation | Reports issues |
| Speed | Fast (single file) | Slower (whole project) |
| Integration | Built-in | Separate tool |
Use both: T-Ruby for development, Steep for validation.
Real-World Example
Complete setup for a production application:
target :app do
check "build/app"
signature "sig/app"
library "pathname"
library "logger"
library "json"
library "net-http"
ignore "build/app/vendor/**"
configure_code_diagnostics do |hash|
hash[D::FallbackAny] = :error
hash[D::UnresolvedOverloading] = :warning
hash[D::IncompatibleAssignment] = :error
end
end
target :test do
check "build/test"
signature "sig/app", "sig/test"
library "minitest"
configure_code_diagnostics do |hash|
hash[D::FallbackAny] = :warning # More lenient for tests
end
end
source:
include:
- src/app
- src/test
output:
ruby_dir: build
rbs_dir: sig
preserve_structure: true
compiler:
strictness: strict
generate_rbs: true
#!/bin/bash
set -e
echo "==> Compiling T-Ruby..."
trc compile src/app --strict
echo "==> Type checking with Steep..."
steep check --target=app
echo "==> Running tests..."
ruby -Ibuild/test -Ibuild/app build/test/all_tests.rb
echo "==> All checks passed!"
Next Steps
- Ruby LSP Integration - IDE support with Steep
- RBS Integration - Learn more about RBS
- Steep Documentation - Official Steep docs