Skip to content

Add EnvironmentLoader for ruby-rbs crate - #3050

Open
dak2 wants to merge 4 commits into
ruby:masterfrom
dak2:environment-loader-ruby-rbs-crate
Open

Add EnvironmentLoader for ruby-rbs crate#3050
dak2 wants to merge 4 commits into
ruby:masterfrom
dak2:environment-loader-ruby-rbs-crate

Conversation

@dak2

@dak2 dak2 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Mirrors RBS::EnvironmentLoader: resolves core, library, and explicit-directory sources in Ruby's load order, then parses each .rbs file once into an Environment.

#2954

Architecture differences from RBS::EnvironmentLoader

  • Ruby's loader both resolves what to load (gems, versions, manifest dependencies) and reads it.
  • The Rust one only reads: it takes resolved directories, so everything needing RubyGems knowledge stays in Ruby, and Rust does the walking, parsing, and interning.
  • That drops the dependency machinery, and makes the name pool per-Environment state instead of a process-global one.
Ruby Rust
Library resolution resolves name/version → directory via gem_sig_path / Repository, and expands manifest.yaml dependencies add_library(name, path) takes an already-resolved directory; no Repository, no manifest expansion, no implicit stringio
Registration API one add(path:/library:/version:) with keyword modes add_library / add_dir, the modes separated by type
Duplicate registration libs is a Set, to stop dependency expansion from recursing plain Vec; with no expansion, first-wins on seen_files is enough
Name pool process-global symbols and TypeName objects Environment owns the Interners, so loaded names stay resolvable for its lifetime
File walk Pathname.glob("**/*.rbs"), sorted as /-joined strings recursive read_dir, sorted by platform path; symlinked directories are not followed, and only PermissionDenied / NotFound are skipped

Usage

Register the directories on the loader, then load them into an Environment:

let loader = EnvironmentLoader::new(Some(core_root))
    .add_library("pathname", pathname_sig_dir) // resolved on the Ruby side
    .add_dir(PathBuf::from("sig"));

let env = Environment::from_loader(&loader)?;

for source in env.sources() {
    // source.path, source.kind (Core / Library { name, path } / Dir { path }),
    // source.directives, source.declarations
}

load can also fill an existing Environment, and returns what it read, in the
order it read it:

let mut env = Environment::new();
let loaded: Vec<LoadedFile> = loader.load(&mut env)?; // { path, kind } per file

Notes

  • Directories are visited core → libraries → dirs, and a path already read is skipped, so the first source registering a file wins.
  • LoadError is Io { path, .. } or Parse { path, message }; the sources read before the failure are already in the Environment.
  • The Environment owns the interners, so env.interners() is what resolves and displays the interned names held by the loaded declarations.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

Comment thread rust/ruby-rbs/src/loader/mod.rs Outdated
Comment thread rust/ruby-rbs/src/environment/source.rs Outdated
Comment thread rust/ruby-rbs/src/loader/gem_sig_resolver.rs Outdated
@dak2
dak2 force-pushed the environment-loader-ruby-rbs-crate branch from 3ffad9d to eddc09a Compare August 14, 2026 00:28
@dak2

dak2 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Remove Buffer. In details, 2b3df5a

@dak2
dak2 marked this pull request as ready for review August 14, 2026 01:37
@dak2
dak2 force-pushed the environment-loader-ruby-rbs-crate branch from 86f3acf to cf765cc Compare August 27, 2026 08:54
dak2 and others added 4 commits August 27, 2026 17:59
A type name id means nothing without the strings it was interned
against, but AstConverter takes the two interners separately and every
caller has to keep them paired. Interners names that pair, so a caller
passes one value and cannot mismatch the halves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Environment owns the Interners, which gives one value the role the
Ruby implementation's global name pool has: names interned while
loading stay resolvable for the environment's lifetime. Alongside them
it collects one Source per file read -- its path, its converted
directives and declarations, and the SourceKind it came from.

SourceKind::Library carries the resolved directory rather than the
version asked for, because the version alone cannot recover where the
signatures came from. The accessors that mutate an environment are
pub(crate), so only this crate's loader appends to one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A file is returned as is, a directory is searched recursively for .rbs
files, and under skip_hidden the subtrees of an underscore-named
directory are left out. Dot entries and symlinked directories are not
traversed, as under Dir.glob.

Two divergences are recorded on the function: the sort compares the
platform separator rather than Ruby's `/`-joined strings, and only
PermissionDenied and NotFound are swallowed where Dir.glob skips every
entry it cannot open or stat, so an unreadable subdirectory is skipped
instead of aborting the scan.

tempfile becomes a dev-dependency for the fixture trees the tests
build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The core root, then the libraries, then the plain directories are
walked in that order, each .rbs file parsed once with the first
registration winning, and one Source appended per file.

It resolves nothing: add_library takes a directory that already holds
the library's RBS files, and manifest.yaml dependencies are not
expanded -- which is also why loading core does not implicitly add
stringio. load returns what this call read, so a caller loading into a
non-empty environment learns what it added, the one question
env.sources() cannot answer.

parse_one takes only the interners so the loop can later be
parallelised per worker; SignatureNode is not Send and must not escape
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dak2
dak2 force-pushed the environment-loader-ruby-rbs-crate branch from cf765cc to 31ff7c8 Compare August 27, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants