Skip to main content

Type System Reference

Complete reference for all types in Infr.

Primitive types​

These map directly to R's atomic vector types:

Infr typeR typeofExamples
numericdouble3.14, 1e10
integerinteger1L, 2L
charactercharacter"hello"
logicallogicalTRUE, FALSE
complexcomplex1+2i
rawrawas.raw(0x1f)

All of these are vectors. numeric means "a numeric vector" (possibly of length 1).

Compound types​

Lists​

const mixed: list <- list(1, "a", TRUE)

Named lists (record-like)​

const person: list<{name: character, age: numeric}> <- list(name = "Alice", age = 30)

person$name # character
person$age # numeric
person$email # Error: Field `email` does not exist

Data frames​

const df: data.frame<{
id: integer,
name: character,
score: numeric
}> <- data.frame(
id = 1:3L,
name = c("Alice", "Bob", "Charlie"),
score = c(95.5, 87.0, 92.3)
)

df$score # numeric
df$nonexistent # Error: Column `nonexistent` does not exist

Sized vectors (scalar types)​

Infr tracks the length of vectors returned by certain built-in functions. A scalar is a vector of length 1, written as T[1]:

ExpressionInferred typeNotes
length(x)integer[1]Always returns a scalar
nrow(df), ncol(df)integer[1]Always returns a scalar
sum(...), mean(x)numeric[1]Aggregation to scalar
min(...), max(...)numeric[1]Aggregation to scalar
any(...), all(...)logical[1]Aggregation to scalar
is.numeric(x), is.null(x), etc.logical[1]Type-checking predicates
which.min(x), which.max(x)integer[1]Index of extremum
nlevels(x)integer[1]Count of factor levels

Sized vectors are assignable to their base type (integer[1] is assignable to integer) and vice versa.

S3 class types (branded types)​

Certain R functions return values with S3 classes that carry semantic meaning beyond their underlying storage type. Infr models these as branded types with a nominal barrier — an S3 class type is not assignable to its base type, preventing accidental misuse:

ExpressionTypeBase type
factor(x), as.factor(x)factorinteger
Sys.Date(), as.Date(x)Datenumeric
as.POSIXct(x)POSIXctnumeric
difftime(t1, t2)difftimenumeric
const today <- Sys.Date()       # type: Date
const n: numeric <- today # Error: Date is not assignable to numeric
const d2: Date <- today # OK

S3 class types with the same class name are assignable to each other.

Special types​

TypeMeaning
anyOpts out of type checking. Escape hatch.
NULLThe R NULL value.
T?Nullable: T or NULL. Shorthand for T | NULL.
neverThe bottom type — no values. Returned by functions that never return (e.g. stop(), stopifnot()).

The never type is a subtype of every type, meaning it is assignable to anything. It vanishes from union types: never | T simplifies to T. This is useful for control flow — when a branch calls stop(), the type of an if/else expression is determined solely by the other branch.

Type aliases​

Define reusable type names with type:

type Score = numeric
type MaybeString = character?
type StringOrNum = character | numeric
type Person = list<{name: character, age: numeric}>

Use them anywhere a type annotation is expected:

add_scores <- function(a: Score, b: Score) -> Score { a + b }
const alice: Person <- list(name = "Alice", age = 30)

Type aliases are erased during transpilation — they produce no R output.

Literal types​

String, numeric, and integer literals can be used as types, representing exact values:

type Greeting = "hello" | "goodbye"
type Dice = 1 | 2 | 3 | 4 | 5 | 6
type Status = "active" | "inactive" | numeric

Literal types are subtypes of their base types — "hello" is assignable to character, but character is not assignable to "hello".

Union types​

const value: numeric | character <- if (flag) 42 else "hello"

Function types​

const transformer: (numeric) -> numeric <- function(x: numeric) -> numeric { x * 2 }

Both function(args) body and the R 4.1+ lambda shorthand \(args) body are supported:

const double <- \(x: numeric) -> numeric x * 2

Overloaded functions​

Functions can have multiple signatures via .d.infr declaration files. When the same function name is declared multiple times, the signatures are merged into an overloaded type:

example.d.infr
#' @infr
as.character <- function(x: numeric) -> character
as.character <- function(x: logical) -> character
as.character <- function(x: complex) -> character

At call sites, Infr resolves the best matching overload based on argument types. If no overload matches, all available signatures are shown in the error message.

Coercion hierarchy​

When combining values with c(), Infr follows R's implicit coercion hierarchy to determine the result type:

logical → integer → numeric → complex → character

The result type is the widest type in the hierarchy that covers all arguments:

c(TRUE, 1L)         # integer (logical coerced to integer)
c(1L, 2.5) # numeric (integer coerced to numeric)
c(1, 1+2i) # complex (numeric coerced to complex)
c(1, "a") # character (numeric coerced to character)
c(TRUE, "yes") # character (logical coerced to character)

tryCatch() return type​

tryCatch() infers a union of the body expression type and all handler return types:

const result <- tryCatch(
readLines("file.txt"), # character
error = function(e) character(0) # character
)
# result type: character (union simplified since both are character)

const result2 <- tryCatch(
as.numeric("abc"), # numeric
warning = function(w) NA # numeric (NA)
)
# result2 type: numeric

If the body or a handler calls stop() (returning never), that branch vanishes from the union.

match.arg() inference​

When a function parameter has a c("a", "b", "c") default and the function body calls match.arg() on that parameter, Infr infers the parameter type as a literal union:

greet <- function(style = c("formal", "casual", "excited")) {
style <- match.arg(style)
# style type: "formal" | "casual" | "excited"
}

greet("formal") # OK
greet("rude") # Error: "rude" is not assignable to "formal" | "casual" | "excited"

This matches the common R idiom for restricting string parameters.

Generics​

Simple type parameters for functions:

identity <- function<T>(x: T) -> T { x }
c <- function<T>(...: T) -> T

Type narrowing​

Infr narrows types inside conditional branches based on type-checking functions:

const x: numeric | character <- get_value()

if (is.numeric(x)) {
x + 1 # x is numeric here
}

const y: numeric? <- maybe_get()
if (!is.null(y)) {
y * 2 # y is numeric here
}

if (inherits(obj, "data.frame")) {
nrow(obj) # obj is data.frame here
}

Supported narrowing functions:

  • is.numeric(), is.character(), is.logical(), is.integer()
  • is.null() / !is.null()
  • inherits()