123456789_123456789_123456789_123456789_123456789_

RBS AST binary serialization

This document describes the binary format used to move a parsed RBS AST out of the parser and into Ruby objects without going through the Ruby C API. It exists so that RBS can run on Ruby implementations that cannot load the C extension (notably JRuby): the parser runs inside WebAssembly, serializes the result with this format, and the host rebuilds ::RBS::AST objects in pure Ruby.

The encoder (rbs_serialize_node, src/serialize.c) and the schema that drives the decoder (::RBS::WASM::SerializationSchema, lib/rbs/wasm/serialization_schema.rb) are both generated from config.yml, so they always agree. The decoder itself is ::RBS::WASM::Deserializer.

Conventions

Nodes

Every node begins with a u8 tag:

A few node types are encoded specially, matching their bespoke handling in ast_translation.c:

Node Payload after tag Decoded as
RBS::AST::Bool u8 true / false
RBS::AST::Integer str String#to_i
RBS::AST::String str the string (UTF-8)
RBS::Types::Record::FieldType node, then u8 [type, required]
RBS::Signature node-list, then node-list [directives, declarations]
::RBS::Namespace node-list, then u8 RBS::Namespace[path, absolute]
::RBS::TypeName node, then node RBS::TypeName[namespace, name]

Every other node is encoded generically:

  1. If the node exposes a location, its base location is written (see below), followed by one location range per declared child, in order.
  2. Each field is written in declaration order, encoded by its type (see below).

The decoder constructs Klass.new(location:, **fields) (omitting location: for nodes that do not expose one). For Class, Module, Interface, TypeAlias and MethodType, RBS::AST::TypeParam.resolve_variables is applied to type_params first, exactly as the C translation does.

Fields

Field type Encoding
node (rbs_node, rbs_type_name, rbs_ast_comment, rbs_ast_symbol, ...) a node (recursive; NULL allowed)
rbs_node_list u32 count, then that many nodes
rbs_hash u32 count, then count × (key node, value node)
rbs_string str (source encoding)
bool u8
enum u8 index into the enum's values (see SCHEMA)
rbs_location_range a location range
rbs_location_range_list u32 count, then that many location ranges
rbs_attr_ivar_name u8 tag: 0nil, 1false, 2str → symbol

Location ranges

A location range is a u8 presence flag:

The base location and child ranges together let the decoder rebuild an ::RBS::Location (with its required/optional children) through the public ::RBS::Location API, so the same decoder works whether ::RBS::Location is backed by the C extension or a pure-Ruby implementation.