Functions

IrFunction is the body-bearing function shape used both for standalone functions on IrModule.functions and for impl methods on IrImpl.functions. IrFunctionSig is the body-less shape used for trait-required methods on IrTrait.methods.

IrFunction

#![allow(unused)]
fn main() {
pub struct IrFunction {
    /// Function name
    pub name: String,

    /// Visibility. A backend keys its export list on this. Serde
    /// defaults it to private.
    pub visibility: Visibility,

    /// Generic type parameters declared on the function or method
    /// itself (e.g. `fn identity<T>(x: T) -> T`,
    /// `fn map<U>(self, f: (T) -> U) -> Box<U>`). Enclosing-type
    /// generics live on the containing `IrImpl` / `IrStruct`. After
    /// `MonomorphisePass`, only an extern method keeps its own: it has
    /// no body to specialise, and each call carries the concrete types.
    pub generic_params: Vec<IrGenericParam>,

    /// Parameters (first is `self` for methods; no `self` for standalone functions)
    pub params: Vec<IrFunctionParam>,

    /// Return type (None = unit/void)
    pub return_type: Option<ResolvedType>,

    /// Function body expression (None for extern functions)
    pub body: Option<IrExpr>,

    /// Calling convention when the function is `extern`. `None` for
    /// regular functions; `Some(ExternAbi::C)` for `extern fn` /
    /// `extern "C" fn`; `Some(ExternAbi::System)` for
    /// `extern "system" fn`.
    pub extern_abi: Option<ExternAbi>,

    /// Codegen-hint attributes (`inline`, `no_inline`, `cold`)
    /// declared as keyword prefixes before `fn`.
    pub attributes: Vec<FunctionAttribute>,

    /// Joined `///` doc comments preceding this function.
    pub doc: Option<String>,

    /// Source span: the declaration of the function.
    pub span: IrSpan,
}

impl IrFunction {
    /// Whether this function is declared `extern`. Convenience
    /// wrapper over `extern_abi.is_some()`.
    pub const fn is_extern(&self) -> bool;
}
}

IrFunctionSig

A signature-only function declaration (no body). Used for required methods declared in traits.

#![allow(unused)]
fn main() {
pub struct IrFunctionSig {
    /// Function name
    pub name: String,

    /// Parameters (first is typically `self`)
    pub params: Vec<IrFunctionParam>,

    /// Return type (None = unit/void)
    pub return_type: Option<ResolvedType>,

    /// Codegen-hint attributes (`inline`, `no_inline`, `cold`).
    pub attributes: Vec<FunctionAttribute>,

    /// Source span.
    pub span: IrSpan,
}
}

IrFunctionParam

#![allow(unused)]
fn main() {
pub struct IrFunctionParam {
    /// Per-function-unique id. Uses of the parameter carry the same id.
    /// Lowering emits `BindingId(0)` and `ResolveReferencesPass`
    /// overwrites it.
    pub binding_id: BindingId,

    /// Parameter name
    pub name: String,

    /// External call-site label, for `fn send(to name: String)`.
    /// `None` when the parameter has no separate label.
    pub external_label: Option<String>,

    /// Parameter type (None for bare `self`)
    pub ty: Option<ResolvedType>,

    /// Default value expression, if any
    pub default: Option<IrExpr>,

    /// Parameter passing convention
    pub convention: ParamConvention,

    /// Source span.
    pub span: IrSpan,
}
}

ParamConvention in the IR

ParamConvention is re-exported from formalang::ast. Backends should interpret it as follows:

VariantMeaning for the backend
LetImmutable read access. The backend may pass by reference or copy.
MutExclusive mutable access. The frontend guarantees no aliasing: see below.
SinkOwnership transfer. The value is logically moved; the caller cannot use it after this call.

No two arguments of one call alias, when one of them is Mut or Sink. The semantic analyser rejects such a call with E144 OverlappingArguments, and the check follows fields: p.x and p.y are two places, while p and p.x are one. So a backend may pass a Mut argument as a raw pointer and add no alias check. A Let argument may still share a value with another Let argument, because nothing changes it.

All three conventions use identical call syntax in FormaLang source; the distinction is purely semantic. Backends that target languages with explicit ownership (Rust, C++ move semantics, Swift inout) should map directly. Backends targeting garbage-collected languages (TypeScript, Python) may treat all three as pass-by-value and ignore the distinction.

#![allow(unused)]
fn main() {
use formalang::ast::ParamConvention;

fn emit_param(param: &IrFunctionParam) {
    match param.convention {
        ParamConvention::Let  => { /* pass by value / reference */ }
        ParamConvention::Mut  => { /* pass as mutable / inout */ }
        ParamConvention::Sink => { /* consume / move */ }
    }
}
}

ExternAbi

The FFI calling convention of an extern function.

#![allow(unused)]
fn main() {
pub enum ExternAbi {
    C,       // `extern fn` (default) or `extern "C" fn`
    System,  // `extern "system" fn`  (stdcall on Win32 x86, C elsewhere)
}
}

Unknown ABI strings (extern "rustcall" fn ...) are rejected at parse time. A backend-side mapping by function name still owns symbol-name overrides and type marshalling rules across the FFI boundary.

FunctionAttribute

#![allow(unused)]
fn main() {
pub enum FunctionAttribute {
    Inline,    // `inline fn`
    NoInline,  // `no_inline fn`
    Cold,      // `cold fn`
}
}

Source syntax stacks freely with pub and extern: pub cold extern fn abort() -> Never. The frontend passes attributes through unchanged; backends decide whether to honour them.