Skip to content

IR Types API

The IR (Intermediate Representation) types provide a language-agnostic representation of functions and types. This abstraction allows parsers and generators to work independently.

Overview

The IR layer is the heart of Polyglot FFI's extensibility:

  • Parsers convert source language → IR
  • Generators convert IR → target language
  • Adding new languages only requires implementing one side

Core Types

IRType

Represents a type in the intermediate representation.

polyglot_ffi.ir.types.IRType dataclass

Language-agnostic type representation.

Examples:

  • Primitive: IRType(kind=PRIMITIVE, name="string")
  • Option: IRType(kind=OPTION, name="option", params=[IRType(...)])
  • List: IRType(kind=LIST, name="list", params=[IRType(...)])
  • Record: IRType(kind=RECORD, name="user", fields={"name": IRType(...), ...})
Source code in src/polyglot_ffi/ir/types.py
@dataclass
class IRType:
    """
    Language-agnostic type representation.

    Examples:
        - Primitive: IRType(kind=PRIMITIVE, name="string")
        - Option: IRType(kind=OPTION, name="option", params=[IRType(...)])
        - List: IRType(kind=LIST, name="list", params=[IRType(...)])
        - Record: IRType(kind=RECORD, name="user", fields={"name": IRType(...), ...})
    """

    kind: TypeKind
    name: str
    params: List["IRType"] = field(default_factory=list)
    fields: Dict[str, "IRType"] = field(default_factory=dict)
    variants: Dict[str, Optional["IRType"]] = field(default_factory=dict)

    def __str__(self) -> str:
        """String representation for debugging."""
        if self.kind == TypeKind.PRIMITIVE:
            return self.name
        elif self.kind == TypeKind.OPTION:
            return f"{self.params[0]} option"
        elif self.kind == TypeKind.LIST:
            return f"{self.params[0]} list"
        elif self.kind == TypeKind.TUPLE:
            types = " * ".join(str(p) for p in self.params)
            return f"({types})"
        elif self.kind == TypeKind.RECORD:
            return f"record {self.name}"
        elif self.kind == TypeKind.VARIANT:
            return f"variant {self.name}"
        return self.name

    def is_primitive(self) -> bool:
        """Check if this is a primitive type."""
        return self.kind == TypeKind.PRIMITIVE

    def is_container(self) -> bool:
        """Check if this is a container type (option, list, etc.)."""
        return self.kind in (TypeKind.OPTION, TypeKind.LIST, TypeKind.TUPLE)

    def is_composite(self) -> bool:
        """Check if this is a composite type (record, variant)."""
        return self.kind in (TypeKind.RECORD, TypeKind.VARIANT)

Functions

__str__()

String representation for debugging.

Source code in src/polyglot_ffi/ir/types.py
def __str__(self) -> str:
    """String representation for debugging."""
    if self.kind == TypeKind.PRIMITIVE:
        return self.name
    elif self.kind == TypeKind.OPTION:
        return f"{self.params[0]} option"
    elif self.kind == TypeKind.LIST:
        return f"{self.params[0]} list"
    elif self.kind == TypeKind.TUPLE:
        types = " * ".join(str(p) for p in self.params)
        return f"({types})"
    elif self.kind == TypeKind.RECORD:
        return f"record {self.name}"
    elif self.kind == TypeKind.VARIANT:
        return f"variant {self.name}"
    return self.name
is_composite()

Check if this is a composite type (record, variant).

Source code in src/polyglot_ffi/ir/types.py
def is_composite(self) -> bool:
    """Check if this is a composite type (record, variant)."""
    return self.kind in (TypeKind.RECORD, TypeKind.VARIANT)
is_container()

Check if this is a container type (option, list, etc.).

Source code in src/polyglot_ffi/ir/types.py
def is_container(self) -> bool:
    """Check if this is a container type (option, list, etc.)."""
    return self.kind in (TypeKind.OPTION, TypeKind.LIST, TypeKind.TUPLE)
is_primitive()

Check if this is a primitive type.

Source code in src/polyglot_ffi/ir/types.py
def is_primitive(self) -> bool:
    """Check if this is a primitive type."""
    return self.kind == TypeKind.PRIMITIVE

TypeKind

Enumeration of all supported type kinds.

polyglot_ffi.ir.types.TypeKind

Bases: Enum

Categories of types in the IR.

IRParameter

Represents a function parameter.

polyglot_ffi.ir.types.IRParameter dataclass

Function parameter representation.

Source code in src/polyglot_ffi/ir/types.py
@dataclass
class IRParameter:
    """Function parameter representation."""

    name: str
    type: IRType

    def __str__(self) -> str:
        return f"{self.name}: {self.type}"

IRFunction

Represents a function signature.

polyglot_ffi.ir.types.IRFunction dataclass

Language-agnostic function representation.

Attributes:

Name Type Description
name str

Function name

params List[IRParameter]

List of parameters

return_type IRType

Return type

doc str

Documentation string

is_async bool

Whether function is async/concurrent

Source code in src/polyglot_ffi/ir/types.py
@dataclass
class IRFunction:
    """
    Language-agnostic function representation.

    Attributes:
        name: Function name
        params: List of parameters
        return_type: Return type
        doc: Documentation string
        is_async: Whether function is async/concurrent
    """

    name: str
    params: List[IRParameter]
    return_type: IRType
    doc: str = ""
    is_async: bool = False

    def __str__(self) -> str:
        params_str = ", ".join(str(p) for p in self.params)
        return f"{self.name}({params_str}) -> {self.return_type}"

    @property
    def arity(self) -> int:
        """Number of parameters."""
        return len(self.params)

Attributes

arity property

Number of parameters.

IRModule

Represents a complete module with functions and types.

polyglot_ffi.ir.types.IRModule dataclass

Top-level module representation.

Contains all functions and type definitions from a source file.

Source code in src/polyglot_ffi/ir/types.py
@dataclass
class IRModule:
    """
    Top-level module representation.

    Contains all functions and type definitions from a source file.
    """

    name: str
    functions: List[IRFunction] = field(default_factory=list)
    type_definitions: List[IRTypeDefinition] = field(default_factory=list)
    doc: str = ""

    def __str__(self) -> str:
        return f"Module {self.name} ({len(self.functions)} functions, {len(self.type_definitions)} types)"

    def get_function(self, name: str) -> Optional[IRFunction]:
        """Get function by name."""
        for func in self.functions:
            if func.name == name:
                return func
        return None

    def get_type(self, name: str) -> Optional[IRTypeDefinition]:
        """Get type definition by name."""
        for typedef in self.type_definitions:
            if typedef.name == name:
                return typedef
        return None

Functions

get_function(name)

Get function by name.

Source code in src/polyglot_ffi/ir/types.py
def get_function(self, name: str) -> Optional[IRFunction]:
    """Get function by name."""
    for func in self.functions:
        if func.name == name:
            return func
    return None
get_type(name)

Get type definition by name.

Source code in src/polyglot_ffi/ir/types.py
def get_type(self, name: str) -> Optional[IRTypeDefinition]:
    """Get type definition by name."""
    for typedef in self.type_definitions:
        if typedef.name == name:
            return typedef
    return None

Type Construction

Primitive Types

Pre-defined primitive type constants:

from polyglot_ffi.ir.types import STRING, INT, FLOAT, BOOL, UNIT

# Use directly
string_type = STRING  # IRType(kind=TypeKind.PRIMITIVE, name="string")
int_type = INT        # IRType(kind=TypeKind.PRIMITIVE, name="int")

Custom Primitive

from polyglot_ffi.ir.types import ir_primitive

custom_type = ir_primitive("bytes")
# IRType(kind=TypeKind.PRIMITIVE, name="bytes")

Option Types

from polyglot_ffi.ir.types import ir_option, STRING

opt_string = ir_option(STRING)
# IRType(kind=TypeKind.OPTION, name="option", params=[STRING])

# Nested options
opt_opt_int = ir_option(ir_option(INT))

List Types

from polyglot_ffi.ir.types import ir_list, STRING

string_list = ir_list(STRING)
# IRType(kind=TypeKind.LIST, name="list", params=[STRING])

# List of options
list_of_opts = ir_list(ir_option(STRING))

Tuple Types

from polyglot_ffi.ir.types import ir_tuple, STRING, INT

pair = ir_tuple(STRING, INT)
# IRType(kind=TypeKind.TUPLE, name="tuple", params=[STRING, INT])

# Triple
triple = ir_tuple(STRING, INT, BOOL)

Record Types

from polyglot_ffi.ir.types import IRType, TypeKind

person_record = IRType(
    kind=TypeKind.RECORD,
    name="person",
    fields={
        "name": STRING,
        "age": INT,
        "email": ir_option(STRING)
    }
)

Variant Types

result_variant = IRType(
    kind=TypeKind.VARIANT,
    name="result",
    fields={
        "Ok": STRING,      # Success case with string payload
        "Error": STRING    # Error case with string payload
    }
)

Function Construction

Simple Function

from polyglot_ffi.ir.types import IRFunction, IRParameter, STRING

encrypt_func = IRFunction(
    name="encrypt",
    parameters=[
        IRParameter(name="input", type=STRING)
    ],
    return_type=STRING,
    doc="Encrypt a string using AES-256"
)

Multiple Parameters

add_func = IRFunction(
    name="add",
    parameters=[
        IRParameter(name="x", type=INT),
        IRParameter(name="y", type=INT)
    ],
    return_type=INT
)

No Parameters

get_version = IRFunction(
    name="get_version",
    parameters=[],
    return_type=STRING
)

Complex Types

from polyglot_ffi.ir.types import ir_option, ir_list

find_func = IRFunction(
    name="find",
    parameters=[
        IRParameter(name="query", type=STRING)
    ],
    return_type=ir_option(STRING),
    doc="Find a value, returns None if not found"
)

map_func = IRFunction(
    name="map",
    parameters=[
        IRParameter(name="items", type=ir_list(STRING))
    ],
    return_type=ir_list(INT)
)

Module Construction

from polyglot_ffi.ir.types import IRModule, IRFunction, IRTypeDefinition

module = IRModule(
    name="crypto",
    functions=[
        IRFunction(
            name="encrypt",
            parameters=[IRParameter(name="data", type=STRING)],
            return_type=STRING
        ),
        IRFunction(
            name="decrypt",
            parameters=[IRParameter(name="data", type=STRING)],
            return_type=STRING
        )
    ],
    type_definitions=[
        IRTypeDefinition(
            name="cipher",
            definition=IRType(kind=TypeKind.VARIANT, name="cipher", fields={
                "AES": UNIT,
                "DES": UNIT
            })
        )
    ],
    doc="Cryptography functions"
)

Type Checking Methods

IRType provides convenient type checking methods:

from polyglot_ffi.ir.types import STRING, ir_option, ir_list, ir_tuple

# Check if primitive
STRING.is_primitive()  # True
ir_option(STRING).is_primitive()  # False

# Check if container (option, list, tuple)
ir_option(STRING).is_container()  # True
ir_list(INT).is_container()  # True
STRING.is_container()  # False

# Check if composite (record, variant)
person_record.is_composite()  # True
STRING.is_composite()  # False

String Representation

All IR types have human-readable string representations:

str(STRING)  # "string"
str(ir_option(STRING))  # "string option"
str(ir_list(INT))  # "int list"
str(ir_tuple(STRING, INT))  # "(string, int)"

func = IRFunction(
    name="encrypt",
    parameters=[IRParameter(name="data", type=STRING)],
    return_type=STRING
)
str(func)  # "encrypt(data: string) -> string"

Usage in Generators

Generators work with IR types to produce target code:

from polyglot_ffi.ir.types import IRType, TypeKind
from polyglot_ffi.type_system.registry import get_default_registry

def generate_python_type(ir_type: IRType) -> str:
    """Convert IR type to Python type hint."""
    registry = get_default_registry()

    if ir_type.kind == TypeKind.PRIMITIVE:
        return registry.get_mapping(ir_type, "python")

    elif ir_type.kind == TypeKind.OPTION:
        inner = generate_python_type(ir_type.params[0])
        return f"Optional[{inner}]"

    elif ir_type.kind == TypeKind.LIST:
        inner = generate_python_type(ir_type.params[0])
        return f"List[{inner}]"

    # ... handle other types

Complete Example

from polyglot_ffi.ir.types import (
    IRModule, IRFunction, IRParameter, IRTypeDefinition,
    STRING, INT, BOOL, ir_option, ir_list, ir_tuple
)

# Define a complete module
api_module = IRModule(
    name="api",
    functions=[
        # Simple function
        IRFunction(
            name="get_version",
            parameters=[],
            return_type=STRING,
            doc="Get API version"
        ),

        # Function with optional return
        IRFunction(
            name="find_user",
            parameters=[IRParameter(name="id", type=INT)],
            return_type=ir_option(STRING),
            doc="Find user by ID, returns None if not found"
        ),

        # Function with list parameters
        IRFunction(
            name="filter_active",
            parameters=[IRParameter(name="users", type=ir_list(STRING))],
            return_type=ir_list(STRING),
            doc="Filter active users"
        ),

        # Function with tuple
        IRFunction(
            name="split_name",
            parameters=[IRParameter(name="full_name", type=STRING)],
            return_type=ir_tuple(STRING, STRING),
            doc="Split full name into (first, last)"
        )
    ],
    doc="User API module"
)

# Use the module
print(f"Module: {api_module.name}")
print(f"Functions: {len(api_module.functions)}")

for func in api_module.functions:
    print(f"  {func.name}: {func.signature}")

See Also