Resolved Types

Every type in the IR is fully resolved. Unlike AST types which use string names, resolved types use IDs that directly reference definitions.

ResolvedType

#![allow(unused)]
fn main() {
pub enum ResolvedType {
    /// Primitive type (String, I32, I64, F32, F64, Boolean, Path, Regex, Never)
    Primitive(PrimitiveType),

    /// Reference to a struct definition
    Struct(StructId),

    /// Reference to a trait definition
    Trait(TraitId),

    /// Reference to an enum definition
    Enum(EnumId),

    /// Array type: [T]
    Array(Box<ResolvedType>),

    /// Range type: T..T: produced by `start..end` expressions and consumed
    /// by `for x in start..end { ... }` loops.
    Range(Box<ResolvedType>),

    /// Optional type: T?
    Optional(Box<ResolvedType>),

    /// Named tuple type: (name1: T1, name2: T2)
    Tuple(Vec<(String, ResolvedType)>),

    /// Generic type instantiation: Box<String> or Option<I32>
    Generic {
        /// The generic struct or enum being instantiated.
        base: GenericBase,
        args: Vec<ResolvedType>,
    },

    /// Unresolved type parameter (T) in generic definitions
    TypeParam(String),

    /// Reference to a type in another module (imported via `use`)
    External {
        module_path: Vec<String>,  // e.g., ["utils", "helpers"]
        name: String,              // Type name
        kind: ImportedKind,        // Struct, Trait, or Enum
        type_args: Vec<ResolvedType>,  // For generics
    },

    /// Dictionary type: [K: V]
    Dictionary {
        key_ty: Box<ResolvedType>,
        value_ty: Box<ResolvedType>,
    },

    /// General closure / function type: (T1, T2) -> R
    ///
    /// Each element is `(convention, type)`: convention constrains the
    /// **caller** of the closure. Event-handler shapes like
    /// `String -> Event` use this variant with the enum return type.
    Closure {
        param_tys: Vec<(ParamConvention, ResolvedType)>,
        return_ty: Box<ResolvedType>,
    },

    /// Typed-out-of-band error placeholder. Produced by IR lowering when an
    /// upstream `CompilerError` has already been pushed but the surrounding
    /// code still needs to materialise *some* `ResolvedType` to keep walking
    /// the AST. Backends should treat `Error` as unreachable: if it survives
    /// to code generation, the compile would already have returned the
    /// associated `CompilerError` to the caller. Replaced the previous
    /// stringly-typed `TypeParam("Unknown")` sentinel.
    Error,
}
}

GenericBase

Target of a Generic instantiation: a generic struct, enum, or trait. Traits appear here only inside generic constraints (<T: Foo<X>>) and impl headers (impl Foo<X> for Y); FormaLang has no dynamic dispatch, so a trait base never sits in a value- type position. Match exhaustively when extracting the underlying ID.

#![allow(unused)]
fn main() {
pub enum GenericBase {
    Struct(StructId),
    Enum(EnumId),
    Trait(TraitId),
}
}

Type Resolution Examples

FormaLang TypeResolvedType
StringPrimitive(PrimitiveType::String)
I32 / I64Primitive(PrimitiveType::I32) / Primitive(PrimitiveType::I64)
F32 / F64Primitive(PrimitiveType::F32) / Primitive(PrimitiveType::F64)
BooleanPrimitive(PrimitiveType::Boolean)
PathPrimitive(PrimitiveType::Path)
RegexPrimitive(PrimitiveType::Regex)
NeverPrimitive(PrimitiveType::Never)
User (local struct)Struct(StructId(n))
Named (local trait)Trait(TraitId(n))
Status (local enum)Enum(EnumId(n))
[String]Array(Box::new(Primitive(String)))
0..10Range(Box::new(Primitive(I32)))
String?Optional(Box::new(Primitive(String)))
[[I32]]Array(Box::new(Array(Box::new(Primitive(I32)))))
Box<String>Generic { base: GenericBase::Struct(StructId(n)), args: [Primitive(String)] }
Option<I32>Generic { base: GenericBase::Enum(EnumId(n)), args: [Primitive(I32)] }
(x: I32, y: I32)Tuple(vec![("x", Primitive(I32)), ("y", Primitive(I32))])
T (in generic)TypeParam("T")
Helper (from use utils::Helper)External { module_path: ["utils"], name: "Helper", ... }
Box<String> (from use containers::Box)External { module_path: ["containers"], name: "Box", type_args: [...] }
[String: I32]Dictionary { key_ty: Primitive(String), value_ty: Primitive(I32) }
String, I32 -> BooleanClosure { param_tys: [(Let, Primitive(String)), (Let, Primitive(I32))], return_ty: Primitive(Boolean) }
mut I32 -> BooleanClosure { param_tys: [(Mut, Primitive(I32))], return_ty: Primitive(Boolean) }
sink String -> BooleanClosure { param_tys: [(Sink, Primitive(String))], return_ty: Primitive(Boolean) }

Display Names

#![allow(unused)]
fn main() {
impl ResolvedType {
    /// Get a display name for this type (useful for debugging/error messages)
    pub fn display_name(&self, module: &IrModule) -> String;
}

// Example usage
let ty = &field.ty;
println!("Field type: {}", ty.display_name(&module));
// Output: "[String]" or "User" or "Box<I32>"
}