Infr — A Gradually Typed Superset of R
Infr (pronounced "infer") adds optional type annotations, const/let bindings, and static type checking to R. All valid R code is valid Infr code — zero annotations means zero errors. Infr transpiles to plain R with no runtime overhead.
See infr-spec.md for the full language specification.
Installation
Download a pre-built binary from GitHub Releases, or install via Cargo:
See the Installation guide for all options including platform-specific binaries.
Quick Start
# Type-check a file
# Type-check and emit .R
# Batch transpile a directory
# Watch mode (re-checks on save)
# Initialize a new project
Example
# script.infr
const -> character {
if (excited) {
} else {
}
}
const msg: character <-
# Data frame with typed columns
const df: data.frame<{id: integer, name: character, score: numeric}> <-
df$score # OK — returns numeric
df$nonexistent # Error: Column `nonexistent` does not exist
Running infr build script.infr produces clean R:
{
if (excited) {
} else {
}
}
msg <-
Features
Bindings
const— prevents reassignment (const x <- 5; x <- 10→ error)let— explicitly mutable binding- Bare
<-— behavior depends on strictness level
Type System
- Primitives:
numeric,integer,character,logical,complex,raw - Nullable:
numeric?(shorthand fornumeric | NULL) - Unions:
numeric | character - Typed lists:
list<{name: character, age: numeric}> - Data frames:
data.frame<{id: integer, name: character}> - Function types:
(numeric, numeric) -> numeric - S4 classes:
S4<ClassName>{slot: type} - Sized vectors:
numeric[3] - Readonly:
readonly numeric - Generics:
function<T>(x: T) -> T
Type Checking
- Type inference from literals, operators, and known functions
- Function signature checking (argument types, return types, arity)
- Data frame column access checking
- Typed list field access checking
- Type narrowing via
is.*()andis.null()in conditionals inherits()narrowing- Pipe (
|>) type propagation - S4 slot access checking
Escape Hatches
anytype — opts out of checking# @infr-ignore— suppresses the next diagnostic# @infr-nocheck— disables checking for the entire file
Strictness Levels
| Level | Bare <- |
Nullable access |
|---|---|---|
relaxed (default) |
No warning | No diagnostic |
moderate |
Warning | Warning |
strict |
Error | Error |
Configure in infr.toml:
[]
= "moderate"
= true
= true
Project Structure
src/
lexer/ # Tokenizer — R + Infr extensions (const, let, :, ->)
mod.rs
token.rs
parser/ # Recursive descent parser — full R grammar + Infr types
mod.rs
ast.rs
types/ # Type definitions and assignability rules
mod.rs
checker/ # Type checker — inference, narrowing, diagnostics
mod.rs
builtins.rs # ~350 built-in function signatures (base, stats, utils, grDevices)
transpiler/ # Emits clean R — strips const/let, type annotations
mod.rs
config/ # TOML configuration (infr.toml)
mod.rs
declarations/ # .d.infr declaration file parser
mod.rs
cli/ # CLI commands: check, build, watch, init, lsp
mod.rs
lsp/ # LSP server (diagnostics, completion, hover)
mod.rs
main.rs # Entry point
declarations/ # Built-in .d.infr type declarations
base.d.infr
stats.d.infr
dplyr.d.infr
tidyr.d.infr
purrr.d.infr
ggplot2.d.infr
readr.d.infr
stringr.d.infr
editors/
vscode/ # VS Code extension (syntax highlighting + LSP client)
tests/
integration_tests.rs # CLI-level integration tests
conformance/ # .infr files with expected diagnostics in #> comments
snapshots/ # Transpilation input/output pairs (.infr → .R)
Development
Prerequisites
- Rust (edition 2024)
- For VS Code extension: Node.js + npm
Building
Running Tests
# Run all tests (unit + integration + conformance + snapshots)
# Run only unit tests
# Run only integration tests
# Run a specific test
# Run with output visible
Test Architecture
Tests are organized at three levels:
1. Unit tests (#[cfg(test)] in each module):
- Lexer: tokenization of R and Infr syntax
- Parser: AST construction for all statement/expression types
- Type system: assignability rules, narrowing operations
- Checker: type checking for all features (const/let, annotations, inference, narrowing, pipes, S4, etc.)
- Transpiler: output correctness for all Infr constructs
- Config: TOML parsing and defaults
2. Integration tests (tests/integration_tests.rs):
- End-to-end CLI tests using the compiled binary
- Tests every feature through
infr checkandinfr build - Includes zero-false-positive tests on plain R code
3. Conformance tests (tests/conformance/*.infr):
- Self-contained
.infrfiles with expected diagnostics as#>comments - Format:
expr #> Error [infr]: message patternorexpr #> OK - Run automatically as part of
cargo test - Easy to add new test cases — just create a new
.infrfile
4. Snapshot tests (tests/snapshots/):
- Pairs of
.infrinput and expected.Routput - Verifies transpilation produces exact expected output
Adding a Conformance Test
Create a .infr file in tests/conformance/:
# tests/conformance/my_feature.infr
# Lines with #> OK expect no error
const x: numeric <- 5 #> OK
# Lines with #> Error expect a matching error
const y: numeric <- "hello" #> Error [infr]: Type mismatch
# Lines with #> Warning expect a matching warning
const df <-
df$b <- 2 #> Warning [infr]: Mutating const binding
VS Code Extension
# To test: open VS Code, press F5 to launch Extension Development Host
# The extension connects to `infr lsp` for diagnostics
LSP Server
# Start the LSP server (used by the VS Code extension)
Declaration Files
Type declarations for R packages use .d.infr files (similar to TypeScript's .d.ts):
# types/mypackage.d.infr
-> logical
-> data.frame
Include them in infr.toml:
[]
= ["types/mypackage.d.infr"]
Built-in declarations are provided for: base, stats, dplyr, tidyr, purrr, ggplot2, readr, stringr.
Architecture
The pipeline is: Source → Lexer → Parser → AST → Type Checker → Transpiler → R output
- Lexer tokenizes R syntax plus Infr extensions (
const,let,:for type annotations,->for return types) - Parser builds a full AST using recursive descent with precedence climbing for expressions
- Type Checker walks the AST, infers types, checks constraints, and emits diagnostics
- Transpiler walks the AST and emits clean R, stripping all Infr-specific syntax
The type system is gradual: unannotated code is unchecked (resolves to any), so existing R code passes through with zero errors. Types are checked only where annotations are present.
License
See the project license file for details.