Type Representation

This document describes how types are represented in the Ori compiler.

Type Enum

Types are represented as an enum in types.rs:

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum Type {
    // Primitive types
    Int,
    Float,
    Bool,
    String,
    Char,
    Byte,
    Void,
    Never,

    // Compound types
    List(Box<Type>),
    Map(Box<Type>, Box<Type>),
    Set(Box<Type>),
    Tuple(Vec<Type>),

    // Optional/Result
    Option(Box<Type>),
    Result(Box<Type>, Box<Type>),

    // Functions
    Function {
        params: Vec<Type>,
        ret: Box<Type>,
        capabilities: Vec<Capability>,
    },

    // User-defined
    Named(Name),
    Struct(StructType),
    Enum(EnumType),

    // Type variables (for inference)
    TypeVar(TypeVarId),

    // Generic instantiation
    Generic {
        base: Box<Type>,
        args: Vec<Type>,
    },

    // Special
    Duration,
    Size,
    Range(Box<Type>),
    Channel(Box<Type>),
    Ordering,
}

Primitive Types

Primitive types are simple variants with no data:

Type::Int    // 64-bit signed integer
Type::Float  // 64-bit floating point
Type::Bool   // Boolean
Type::String // UTF-8 string
Type::Char   // Unicode codepoint
Type::Byte   // 8-bit unsigned
Type::Void   // Unit type
Type::Never  // Bottom type (for panic, etc.)

Compound Types

Compound types contain other types:

// List of integers
Type::List(Box::new(Type::Int))

// Map from string to int
Type::Map(Box::new(Type::String), Box::new(Type::Int))

// Tuple of (int, bool)
Type::Tuple(vec![Type::Int, Type::Bool])

// Option<String>
Type::Option(Box::new(Type::String))

// Result<int, Error>
Type::Result(Box::new(Type::Int), Box::new(Type::Named(error_name)))

Function Types

Function types include parameter types, return type, and capabilities:

// (int, int) -> int
Type::Function {
    params: vec![Type::Int, Type::Int],
    ret: Box::new(Type::Int),
    capabilities: vec![],
}

// (str) -> Result<str, Error> uses Http
Type::Function {
    params: vec![Type::String],
    ret: Box::new(Type::Result(
        Box::new(Type::String),
        Box::new(Type::Named(error_name)),
    )),
    capabilities: vec![Capability::Http],
}

User-Defined Types

Named Types

Simple reference to a user-defined type:

// type Point = { x: int, y: int }
Type::Named(point_name)  // References "Point" in TypeRegistry

Struct Types

Inline struct definition:

Type::Struct(StructType {
    name: point_name,
    fields: vec![
        Field { name: x_name, ty: Type::Int },
        Field { name: y_name, ty: Type::Int },
    ],
})

Enum Types

Sum types with variants:

Type::Enum(EnumType {
    name: option_name,
    variants: vec![
        Variant::Unit(some_name),
        Variant::Tuple(none_name, vec![Type::TypeVar(t)]),
    ],
})

Type Variables

Type variables are used during type inference:

#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
pub struct TypeVarId(pub u32);

// Fresh type variable
Type::TypeVar(TypeVarId(0))

Type variables are resolved through unification:

// Initially: TypeVar(0)
// After inference: Int

Generic Types

Generic type instantiation:

// List<int> (generic list with int argument)
Type::Generic {
    base: Box::new(Type::Named(list_name)),
    args: vec![Type::Int],
}

// Map<str, User>
Type::Generic {
    base: Box::new(Type::Named(map_name)),
    args: vec![Type::String, Type::Named(user_name)],
}

Type Comparison

Types implement Eq for structural comparison:

let t1 = Type::List(Box::new(Type::Int));
let t2 = Type::List(Box::new(Type::Int));
let t3 = Type::List(Box::new(Type::Float));

assert_eq!(t1, t2);  // Same structure
assert_ne!(t1, t3);  // Different element type

Type Display

Types can be formatted for error messages:

impl Display for Type {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Type::Int => write!(f, "int"),
            Type::List(elem) => write!(f, "[{}]", elem),
            Type::Option(inner) => write!(f, "Option<{}>", inner),
            Type::Function { params, ret, .. } => {
                write!(f, "({}) -> {}", params.join(", "), ret)
            }
            // ...
        }
    }
}

Type Registry

User-defined types are stored in TypeRegistry:

pub struct TypeRegistry {
    types: HashMap<Name, TypeDef>,
}

pub enum TypeDef {
    Struct(StructDef),
    Enum(EnumDef),
    Alias(Type),
}

Looking up a named type:

// Type::Named(point_name)
let def = registry.get(point_name)?;

Salsa Compatibility

The Type enum derives all required traits:

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum Type { ... }

This allows types to be:

  • Stored in Salsa query results
  • Compared for early cutoff
  • Hashed for memoization

Boxing Strategy

Recursive types use Box<Type> to avoid infinite size:

// Would be infinite size without Box:
enum Type {
    List(Type),  // Error: infinite size
}

// Fixed with Box:
enum Type {
    List(Box<Type>),  // OK: Box has fixed size
}

The boxing overhead is acceptable because:

  • Types are not allocated frequently (once per expression)
  • Deep nesting is rare in practice
  • Comparison uses structural equality anyway

Special Types

Duration and Size

Built-in types for literals:

Type::Duration  // 100ms, 5s, 2h
Type::Size      // 4kb, 10mb, 2gb

Range

Range type for iterators:

Type::Range(Box::new(Type::Int))  // Range<int>

Channel

Channel type for concurrency:

Type::Channel(Box::new(Type::String))  // Channel<str>

Ordering

Comparison result type:

Type::Ordering  // Less | Equal | Greater