Skip to content

Commit

Permalink
Merge pull request #1697 from ruby/architecture.md
Browse files Browse the repository at this point in the history
Add docs/architecture.md
  • Loading branch information
soutaro authored Dec 22, 2023
2 parents 0b0960f + 55cac06 commit 0137146
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ puts singleton.methods[:gsub]

## Guides

- [Architecture](docs/architecture.md)
- [Core and standard library signature contribution guide](docs/CONTRIBUTING.md)
- [Writing signatures guide](docs/sigs.md)
- [Stdlib signatures guide](docs/stdlib.md)
Expand Down
110 changes: 110 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 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.

- `RBS::Parser.parse_method_type` parsers a *method type*. (`[T] (String) { (IO) -> T } -> Array[T]`)
- `RBS::Parser.parse_type` parses a *type*. (`Hash[Symbol, untyped]`)
- `RBS::Parser.parse_signature` parses the whole RBS file.

### 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:

```rbs
module Hello
class World
end
end
class Hello::World
end
```

And the environment organizes the definitions as follows:

- There are two classes `::Hello` and `::Hello::World`
- It provides a mapping from `::Hello` to it's `module` declaration and `::Hello::World` to it's two `class` declarations

### Definition and DefinitionBuilder

`RBS::Definition` tells you:

- The set of available methods in a class/module/interface
- The set of instance variables in a class/module
- The ancestors in a class/module

Definition is constructed for:

- A singleton class of a class/module -- `singleton(String)`, `singleton(Array)`,
- An instance of a class -- `String`, `Array[T]`, or
- An interface -- `_ToS`

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.

- `DefinitionBuilder#build_singleton` returns a definition of singleton classes of given class/module.
- `DefinitionBuilder#build_instance` returns a definition of instances of given class/module.
- `DefinitionBuilder#build_interface` returns a definition of interfaces.

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.

```ruby
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.

0 comments on commit 0137146

Please sign in to comment.