Type System Reference
Complete reference for all types in Infr.
Primitive types
These map directly to R's atomic vector types:
| Infr type | R typeof | Examples |
|---|---|---|
numeric | double | 3.14, 1e10 |
integer | integer | 1L, 2L |
character | character | "hello" |
logical | logical | TRUE, FALSE |
complex | complex | 1+2i |
raw | raw | as.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]:
| Expression | Inferred type | Notes |
|---|---|---|
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:
| Expression | Type | Base type |
|---|---|---|
factor(x), as.factor(x) | factor | integer |
Sys.Date(), as.Date(x) | Date | numeric |
as.POSIXct(x) | POSIXct | numeric |
difftime(t1, t2) | difftime | numeric |
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
| Type | Meaning |
|---|---|
any | Opts out of type checking. Escape hatch. |
NULL | The R NULL value. |
T? | Nullable: T or NULL. Shorthand for T | NULL. |
never | The 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:
#' @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()