Skip to main content
View

How to build, test, and document a TypR package

A TypR package is a normal R package with one extra folder. This guide covers the full development workflow: building, testing, and documenting.

Package structure​

A TypR package follows standard R package conventions:

mypackage/
  DESCRIPTION
  NAMESPACE
  R/            # generated R code (do not edit by hand)
  TypR/         # your typed source code
    main.ty     # entry point (mandatory)
    utils.ty    # additional modules
  man/          # documentation (auto-generated)
  tests/        # testthat tests
  mypackage.Rproj

The TypR/ folder is the only addition. Everything else, DESCRIPTION, NAMESPACE, R/, man/, tests/, is standard R.

Step 1: Build the package​

Write your typed code in TypR/main.ty:

# a signature types the base-R `paste` so the call below is checked
@paste: (...values: Any) -> char;

type Person <- list {
  name: char,
  age: int
};

let new_person <- fn(name: char, age: int): Person {
  list(name = name, age = age)
};

let greet <- fn(p: Person): char {
  paste("Hello,", p$name)
};

print(greet(new_person("Alice", 25)));

Run the build from the package root:

typr build

This transpiles every .ty file in TypR/ into R code in R/:

R/
  a_std.R                # generated helpers
  b_generic_functions.R  # generated helpers
  c_types.R              # generated type definitions
  d_main.R               # transpiled main.ty

The naming convention (a_, b_, c_, d_) ensures correct load order.

Step 2: Test with testthat​

TypR has a built-in Test block that extracts into standard testthat files during transpilation:

# --- setup, from step 1 ---
@paste: (...values: Any) -> char;
type Person <- list { name: char, age: int };
let new_person <- fn(name: char, age: int): Person { list(name = name, age = age) };
let greet <- fn(p: Person): char { paste("Hello,", p$name) };
# --------------------------

Test {
  test_that("new_person creates a valid person", {
    let p <- new_person("Alice", 25);
    expect_equal(p$name, "Alice");
    expect_equal(p$age, 25);
  });

  test_that("greet returns a greeting string", {
    let p <- new_person("Bob", 30);
    expect_equal(greet(p), "Hello, Bob");
  })
}

Run tests as you would for any R package:

devtools::test()
# or
testthat::test_local()
tip

Place Test blocks directly after the functions they test. This keeps logic and tests side by side and the transpiler extracts them into tests/testthat/ automatically.

Step 3: Document with roxygen2​

TypR generates roxygen2-compatible comments from your type annotations. The transpiler infers @param, @return, and @export directives from the function signatures:

# --- setup, from step 1 ---
@paste: (...values: Any) -> char;
type Person <- list { name: char, age: int };
# --------------------------

@pub let greet <- fn(p: Person): char {
  paste("Hello,", p$name)
};

The @pub keyword generates an @export directive. After transpilation, run:

devtools::document()

This populates man/ with .Rd files and updates NAMESPACE.

Step 4: Install and check​

devtools::install()
devtools::check()

The package installs and checks like any standard R package. CRAN, R CMD check, and pkgdown all see regular R code, TypR is invisible at this stage.

Step 5: Use modules for clean organization​

As your package grows, split code into one file per type or concept:

# main.ty
mod person;
mod utils;

TypR resolves mod person to TypR/person.ty and generates R/person.R. This keeps the codebase modular without any extra configuration.

See Modules & Imports for the full module system.

Incremental migration​

You do not have to convert an entire R package at once. The recommended approach:

  1. Add a TypR/ folder to your existing package
  2. Move one function at a time from R/ to TypR/
  3. Run typr build to regenerate the R code
  4. Run devtools::test() to verify nothing broke
  5. Repeat

TypR never forces an all-or-nothing choice.

Common build issues​

ProblemSolution
main.ty not foundEnsure TypR/main.ty exists (mandatory entry point)
Collision with R/ fileTypR overwrites R/<name>.R when TypR/<name>.ty exists. Do not edit generated files by hand.
Missing type errorsRun typr build and the compiler reports type errors before generating R code
@importFrom not in NAMESPACERun devtools::document() after typr build

Where to go next​