Development
This page helps you get from a clone to passing tests and explains a few non-obvious corners of the codebase.
Project layout
Section titled “Project layout”ry is a Rust workspace. The shipping language tool is six crates:
crates/syntax— the hand-written lexer and recursive-descent parser, producing lossless rowan syntax trees;#:type annotations are first-class grammar, not comment text.crates/semantics— the salsa-based analysis core: the item tree, HIR lowering, naming, the Hindley–Milner type checker, stubs, lints, and diagnostics.crates/format— the non-invasive formatter (depends only onsyntax).crates/ide— editor features as pure reads oversemantics: hover, navigation, rename, completion, signature help, inlay hints, symbols, code actions.crates/ry— the product binary: the CLI (check,fmt,server,repl,run) and the LSP server.crates/repl— the interactive R console behindry replandry run, embedding the system R by locating and loading it at runtime.
The workspace also contains the frozen legacy stack (legacy/analysis-legacy,
legacy/engine-legacy, legacy/ry-legacy, plus its legacy/fixtures harness): the previous
implementation, kept in-tree only as the benchmark baseline for legacy/differential. The parity
program that once ran every fixture through both stacks is complete and retired. Do not extend the
legacy stack, and never share or abstract code between the two stacks — data files may be
duplicated freely instead.
The console embeds R without any build-time link dependency: the R shared library is located (R_HOME, or
R RHOME from PATH) and loaded at runtime, so the whole workspace builds and its unit tests run
on machines with no R at all. Only running the console needs R; it runs on Unix (through the
ptr_R_ReadConsole hook globals) and on Windows (through the Rstart callback struct). Its
end-to-end tests (cargo test -p ry-lang --test test_repl_e2e) drive the real binary through a
pseudo-terminal (a Unix pty or Windows ConPTY) and skip cleanly where no R exists — run them
locally before touching the REPL. The predecessor
experiment (legacy/rofy, which linked R at build time through extendr) stays frozen until the
new console reaches feature parity.
The analysis design is documented in Architecture and the file layout in
Structure. The editor extensions live under editors/ (code for VS Code, zed for
Zed).
Build and test
Section titled “Build and test”The repo-root justfile names every day-to-day action; just (with no arguments) lists them.
The ones that matter most:
just gate # the full per-slice gate: battery, clippy -D warnings, fmt checkjust battery # the workspace test battery (excludes rofy and zed_ry)just fixture <group__case> # one focused fixture casejust bless -p semantics --test ... # re-bless fixture expectations (review the diff!)just fuzz-deep # the long-running seeded fuzz pass (FUZZ_ITERS scales)just fuzz-run <target> # one coverage-guided fuzz targetjust stats <path> # the workspace performance diagnosisThe raw commands, for environments without just:
cargo build # the product crate (workspace default member)cargo test # the product crate's suitescargo test --workspace --exclude rofy --exclude zed_ry # everything: all six crates, the benchmark # harness, and the frozen legacy stackcargo test -p semantics # the analysis core's fixture + fuzz suitescargo test -p format --test test_format_fixturesMost behavior is verified with fixture tests — human-readable .test files rendered to expected
output. Read Testing for the fixture contract before adding or changing tests. Two
environment variables matter day to day:
RY_BLESS=1rewrites the expected#++++blocks in place from the current output (review the diff before committing).FIXTURE_FILTER=group__caseruns a single fixture case.
The real-world corpus some suites and all measurement instruments use is fetched with
scripts/fetch-corpus.rs (a cargo +nightly -Zscript single-file script) into the gitignored
corpus/; the resolved inventory is committed as scripts/corpus-manifest.txt. The performance and memory instruments live in
legacy/differential/tests/test_stats.rs and are documented on the Testing page.
Debug mode
Section titled “Debug mode”ry server --debug surfaces internal analysis facts in the editor — hovers gain a “Debug”
section with the Lowering, Naming, and Parsing views of the expression under the cursor.
RY_DEBUG=1 in the server’s environment is the equivalent for setups where editing the
server’s arguments is awkward; the flag takes precedence. This is deliberately not a
ry.toml key: the config file is user-facing contract, and this switch is an aid for people
working on ry itself.
Diagnosing a slow workspace
Section titled “Diagnosing a slow workspace”ry debug analysis-stats [path] runs the full analysis pipeline over a workspace through the
same queries the language server uses and reports where the time and memory go: per-phase wall time
with resident-set growth (load, parse, lower + naming, typecheck, diagnostics), the slowest files
by typecheck, and an incremental typing probe on representative files (keystroke latency, item
rechecks and resolve steps per keystroke, and the raw re-parse floor). It forces [check] typing
on — a diagnosis without the type checker measures nothing interesting — and says so when the
configuration had it off. Build with --release when the absolute numbers matter; a debug build
still shows honest ratios. ry debug ast <file> prints a file’s syntax tree.
To work on this documentation site, run just docs (a live preview) or cd docs && npx astro build.
The formatter page (docs/src/content/docs/reference/formatting-rules.md) is generated — edit
crates/format/tests/formatter.template.md and regenerate with
RY_BLESS=1 cargo test -p format --test test_format_docs instead.
VS Code Extension Setup
Section titled “VS Code Extension Setup”- Run
cd editors/code && bun install. This installs all necessary npm modules - Press Ctrl+Shift+B in VS Code to start compiling the client in watch mode.
- Switch to the Run and Debug View in the Sidebar (Ctrl+Shift+D).
- Select
Launch Clientfrom the drop down (if it is not already). - Press ▷ to run the launch config (F5).
- In the Extension Development Host instance of VSCode, open a document with a
.Rextension.
If the language server never spawns
Section titled “If the language server never spawns”launch.json sets "autoAttachChildProcesses": true. Some debugger extensions — CodeLLDB
installed from nixpkgs is a known case — intercept the child process and the server never
starts. Disable the extension or turn that setting off.
Formatting: why the code is imperative
Section titled “Formatting: why the code is imperative”The main challenge in formatting R is comments, which may appear between any two tokens — inside an
if header, between a call and its argument list, after an operator. A concise “format each field”
style silently drops them, so the formatter walks concrete children token-by-token and makes
placement decisions at the token level. The same constraint shapes the rowan tree handling: trailing
comments attach inside expression nodes, so closer-placement decisions must look at tokens, never
whole elements. See crates/format/src/format.rs and the formatter fixtures under
crates/format/tests/format/.
References
Section titled “References”- https://github.com/wch/r-source/blob/trunk/src/main/gram.y
- https://cran.r-project.org/doc/manuals/r-release/R-lang.html
- https://www.reddit.com/r/rust/comments/uu47mk/comment/i9dn0yg/
VS Code Extensions
Section titled “VS Code Extensions”- docs:
- examples:
Other Language Servers written in Rust
Section titled “Other Language Servers written in Rust”- https://github.com/gleam-lang/gleam/tree/main/compiler-core/src/language_server
- https://github.com/supabase-community/postgres-language-server
- tower-lsp
- https://github.com/Desdaemon/-lsp/
- https://github.com/FuelLabs/sway
- https://github.com/IWANABETHATGUY/tower-lsp-boilerplate
- https://github.com/TenStrings/glicol-lsp/blob/77e97d9c687dc5d66871ad5ec91b6f049de2b8e8/src/main.rs#L16
- https://github.com/Automattic/harper
- https://github.com/jfecher/ante/blob/5f7446375bc1c6c94b44a44bfb89777c1437aaf5/ante-ls/src/main.rs#L163
- async_lsp
Formatting
Section titled “Formatting”Language Design / Typing
Section titled “Language Design / Typing”- https://github.com/Glyphack/enderpy
- https://github.com/dgkf/R
- https://github.com/fabriceHategekimana/typr
- https://github.com/salsa-rs/salsa/blob/master/examples/calc/type_check.rs