Modern developer tooling for R.

Type checker, language server, and formatter in one binary. Catch errors before you run the code — without changing the R you already write.

three tools, one binary

Three tools, one binary.

Built for R users who have outgrown RStudio: editor smarts, problem-catching analysis, and one consistent style from a single binary. R has world-class statistics — ry brings the IDE-grade tooling other ecosystems already take for granted, fast enough to scale past 300k lines.

Language server

Completion, hover, go-to-definition, references, rename, and inlay hints — live in your editor.

Language server

Code analyzer

Lint diagnostics on by default — plus the first static type checker for R, opt-in.

Type checker

Formatter

ry fmt rewrites your whole project to one consistent style, with a --diff mode for CI.

Formatter

write

Your editor, fluent in R.

One server behind every editor feature — complete a record's fields, hover a name for its inferred signature, jump to where it is bound, rename it everywhere at once. Extensions for VS Code and Zed, plain LSP for the rest.

sales.R report.R ry
# one sale, and what is left of it after fees
sale: list{region: character, amount: double, closed: logical} <- list(region = "north", amount = 1240.5, closed = TRUE)
margin: <T: numeric, U: numeric> fn(gross: T, rate: U) -> double <- function(gross, rate) gross * (1 - rate)
net: double <- margin(sale$amount, 0.18)
label: character <- paste0(sale$region, ": ", round(net, 2))
message(label)
3 references
R/sales.R4:1margin <- function(gross, rate) gross * (1 - rate)
R/sales.R6:8net <- margin(sale$amount, 0.18)
R/report.R2:28paste0(sale$region, " ", margin(sale$amount, rate))
The fields of a record type, each with its inferred type. The inferred type, and where the name is bound. Jump to the binding — in this file, another file, or a stdlib stub. Every read and write of the name, across the whole workspace. One rename, applied everywhere the name is bound. Inferred types shown inline. Nothing was annotated here.

Language server docs

check

Catch it before you run it.

ry check lints out of the box. Switch the type checker on and it reads your code the way a compiler would — and since inference runs whether or not you annotate anything, it catches the mistake either way.

R/sales.R no configuration
read_sales <- function(path) {
read.csv(path, stringsAsFactors = FALSE)
}
top_regions <- function(sales, n) {
ranked = sales[order(-sales$amount), ]
head(unique(ranked$region), n)
}
report <- function(sales, regions) {
message(nrwo(sales), " rows in ", length(regions), " regions")
invisible(regions)
}
sales <- read_sales("sales.csv")
regions <- top_regions(sales, 5)
verbose <- T
if (verbose) report(sales, regions)
$ ry check
assignment-operator
! Use <-, not =, for assignment
--[R/sales.R:6:10]
5 | top_regions <- function(sales, n) {
6 | ranked = sales[order(-sales$amount), ]
| ^
7 | head(unique(ranked$region), n)
unresolved
! I could not resolve `nrwo` in this package, its imports, or builtins. Did you mean `nrow`?
--[R/sales.R:11:11]
10 | report <- function(sales, regions) {
11 | message(nrwo(sales), " rows in ", length(regions), " regions")
| ^^^^
12 | invisible(regions)
boolean-shorthand
! Use TRUE, not T, for Boolean values
--[R/sales.R:18:12]
17 |
18 | verbose <- T
| ^
19 | if (verbose) report(sales, regions)
3 problems in 1 file
Lints and unresolved names need no setup. Each diagnostic names the code you would put in a # ry: allow(...) comment. exit 1

Type checker guide Linter rules

ship

One consistent style, in place.

ry fmt is deliberately non-invasive. It fixes spacing, braces and indentation, and it never splits a line you chose to keep on one line. Run it across the project, or use --diff in CI.

totals.R operators, commas, quotes
as written
totals<-sapply(sales,function(s)s$amount)
top<-sales[totals>threshold ,]
message(paste0('kept: ',length(top),' of ',length(sales)))
ry fmt
totals <- sapply(sales, function(s) s$amount)
top <- sales[totals > threshold, ]
message(paste0("kept: ", length(top), " of ", length(sales)))
$ ry fmt --diff totals.R 1 file would be reformatted, 0 files already formatted

Formatter rules

adopt

Drop it onto your R project.

Nothing about your existing R has to change. Install the VS Code extension in one click — no toolchain required — or grab the CLI and point ry at your project.

1 · Install

cargo install --git https://github.com/felix-andreas/ry ry-lang

2 · Run it on your project

ry fmt .     # format
ry check .   # lint and type-check

Grab the VS Code extension from the Marketplace, then tune behavior in configuration.