123456789_123456789_123456789_123456789_123456789_

Architecture

This guide describes the outline of the architecture of RBS library. It helps you to understand the structure and key features of the library to start contributing to the library.

Bird's Eye View

The goal of the library is simple: Read RBS files and generate the structure of Ruby programs.

RBS files
           -- RBS::Parser
Syntax tree
  
Environment
          -- Definition builder
Definition

The input is RBS files. The gem ships with RBS type definitions of Ruby core library and some of the standard libraries. You write RBS files for your applications or gems.

Syntax tree is the next representation. ::RBS::Parser transforms the sequence of characters in RBS files into syntax trees.

Syntax tree objects are loaded to ::RBS::Environment. It collects loaded RBS objects, organizes the definitions, and provides some utilities, like resolving type names and finding the declarations.

::RBS::Definition is the goal of the transformation steps. It is associated with a class singleton, a class object, or an interface. You can find the list of available methods and their types, instance variables, and class hierarchies.

Core classes

Types

Types are defined under ::RBS::Types, like ::RBS::Types::ClassInstance or ::RBS::Types::Union. You will find the definition of each type supported in RBS.

Parsing RBS files

The RBS source code is loaded into ::RBS::Buffer, and ::RBS::Parser is the parser. The parser is implemented in C extension.

::RBS::Parser provides three entrypoints.

Environment

RBS AST is loaded to ::RBS::Environment by ::RBS::EnvironmentLoader. Environment gives absolute names to the declarations, and provides an index from the absolute name to their declarations.

Assume we have the following nested RBS declarations:

module Hello
  class World
  end
end

class Hello::World
end

And the environment organizes the definitions as follows:

Definition and DefinitionBuilder

::RBS::Definition tells you:

Definition is constructed for:

Note that generic class instances/interfaces are kept generic. We don't have a definition of Array[String] but of Array[T].

DefinitionBuilder constructs Definition of given type names.

It uses AncestorBuilder to construct ancestor chains of the type. MethodBuilder constructs sets of available methods based on the ancestor chains.

The #build_singleton calculates the type of .new methods based on the definition of #initialize method. This is different from Ruby's implementation -- it reused Class#new method but we need the custom implementation to give precise .new method type of each class.

Working with type aliases

DefinitionBuilder#expand_alias and its variants provide one step unfold operation of type aliases.

builder.expand_alias2(TypeName("::int"), []) # => returns `::Integer | ::_ToInt`

We don't have normalize operation for type aliases, because RBS allows recursive type alias definition, which cannot be fully unfolded.

Other utilities

::RBS::Validator provides validation of RBS type declaration. It validates that all of the type name references can be resolved, all type applications have correct arity, and so on.

::RBS::Test provides runtime type checking, which confirms if a Ruby object can have an RBS type. It also provides an integration to existing Ruby code so that we run Ruby code, assuming unit tests, with runtime type checking.

::RBS::UnitTest provides utilities to help write unit tests for RBS type definitions. Use the tool to make sure your RBS type definition is consistent with implementation.

::RBS::Prototype is the core of rbs prototype feature. It scans Ruby source code or uses reflection features, and it generates the prototype of RBS files.

::RBS::Collection includes rbs collection features.