Skip to content

Type System API

The type system manages mappings between IR types and target language types, ensuring consistent type translation across all generators.

Overview

The type system provides:

  • Type Registry: Central repository for type mappings
  • Built-in Types: Standard primitive type mappings
  • Custom Types: User-defined type mappings
  • Type Validation: Ensure types are supported
  • Caching: O(1) lookup performance

Type Registry

polyglot_ffi.type_system.registry.TypeRegistry

Registry for type mappings between IR types and target language types.

Example usage

registry = TypeRegistry() registry.register_primitive("string", { "ocaml": "string", "python": "str", "c": "char*" })

python_type = registry.get_mapping("string", "python") # Returns "str"

Source code in src/polyglot_ffi/type_system/registry.py
class TypeRegistry:
    """
    Registry for type mappings between IR types and target language types.

    Example usage:
        registry = TypeRegistry()
        registry.register_primitive("string", {
            "ocaml": "string",
            "python": "str",
            "c": "char*"
        })

        python_type = registry.get_mapping("string", "python")  # Returns "str"
    """

    def __init__(self):
        self._primitive_mappings: Dict[str, Dict[str, str]] = {}
        self._custom_converters: Dict[str, Dict[str, Callable]] = {}
        # Cache for type mappings (cleared when registry is modified)
        self._mapping_cache: Dict[tuple, str] = {}

    def register_primitive(self, ir_type_name: str, mappings: Dict[str, str]) -> None:
        """
        Register a primitive type mapping.

        Args:
            ir_type_name: Name of the IR type (e.g., "string", "int")
            mappings: Dictionary of language -> type_name mappings
        """
        self._primitive_mappings[ir_type_name] = mappings
        self._mapping_cache.clear()  # Clear cache when registry is modified

    def register_converter(
        self, ir_type_name: str, target_lang: str, converter: Callable[[IRType], str]
    ) -> None:
        """
        Register a custom type converter function.

        Args:
            ir_type_name: Name of the IR type
            target_lang: Target language
            converter: Function that takes IRType and returns type string
        """
        if ir_type_name not in self._custom_converters:
            self._custom_converters[ir_type_name] = {}
        self._custom_converters[ir_type_name][target_lang] = converter
        self._mapping_cache.clear()  # Clear cache when registry is modified

    def _type_to_cache_key(self, ir_type: IRType) -> tuple:
        """Convert IRType to a hashable cache key."""
        params_key = (
            tuple(self._type_to_cache_key(p) for p in ir_type.params) if ir_type.params else ()
        )
        return (ir_type.kind.value, ir_type.name, params_key)

    def get_mapping(self, ir_type: IRType, target_lang: str) -> str:
        """
        Get the type mapping for a target language.

        Args:
            ir_type: The IR type to map
            target_lang: Target language (e.g., "python", "c", "rust")

        Returns:
            Type string in the target language

        Raises:
            TypeMappingError: If no mapping exists
        """
        # Check cache first
        cache_key = (self._type_to_cache_key(ir_type), target_lang)
        if cache_key in self._mapping_cache:
            return self._mapping_cache[cache_key]

        # Compute the mapping
        result = self._compute_mapping(ir_type, target_lang)

        # Store in cache
        self._mapping_cache[cache_key] = result
        return result

    def _compute_mapping(self, ir_type: IRType, target_lang: str) -> str:
        """
        Compute the type mapping for a target language (internal, uncached).

        Args:
            ir_type: The IR type to map
            target_lang: Target language

        Returns:
            Type string in the target language

        Raises:
            TypeMappingError: If no mapping exists
        """
        # Handle primitive types
        if ir_type.kind == TypeKind.PRIMITIVE:
            if ir_type.name in self._primitive_mappings:
                mappings = self._primitive_mappings[ir_type.name]
                if target_lang in mappings:
                    return mappings[target_lang]
                raise TypeMappingError(
                    f"No {target_lang} mapping for primitive type '{ir_type.name}'"
                )
            raise TypeMappingError(f"Unknown primitive type '{ir_type.name}'")

        # Handle option types
        elif ir_type.kind == TypeKind.OPTION:
            if not ir_type.params:
                raise TypeMappingError("Option type must have a parameter")

            inner_type = self.get_mapping(ir_type.params[0], target_lang)

            if target_lang == "python":
                return f"Optional[{inner_type}]"
            elif target_lang == "c":
                # In C, we can use a struct with a flag
                return f"{inner_type}*"  # Nullable pointer
            elif target_lang == "ocaml":
                return f"{inner_type} option"
            elif target_lang == "rust":
                return f"Option<{inner_type}>"
            else:
                raise TypeMappingError(f"No option type support for {target_lang}")

        # Handle list types
        elif ir_type.kind == TypeKind.LIST:
            if not ir_type.params:
                raise TypeMappingError("List type must have a parameter")

            inner_type = self.get_mapping(ir_type.params[0], target_lang)

            if target_lang == "python":
                return f"List[{inner_type}]"
            elif target_lang == "c":
                # In C, lists need special handling with structs
                return f"{inner_type}*"  # Array pointer
            elif target_lang == "ocaml":
                return f"{inner_type} list"
            elif target_lang == "rust":
                return f"Vec<{inner_type}>"
            else:
                raise TypeMappingError(f"No list type support for {target_lang}")

        # Handle tuple types
        elif ir_type.kind == TypeKind.TUPLE:
            if not ir_type.params:
                raise TypeMappingError("Tuple type must have parameters")

            tuple_types = [self.get_mapping(p, target_lang) for p in ir_type.params]

            if target_lang == "python":
                types_str = ", ".join(tuple_types)
                return f"Tuple[{types_str}]"
            elif target_lang == "c":
                # In C, tuples need struct definitions
                return "tuple_t"  # Placeholder - needs actual struct
            elif target_lang == "ocaml":
                types_str = " * ".join(tuple_types)
                return f"({types_str})"
            elif target_lang == "rust":
                types_str = ", ".join(tuple_types)
                return f"({types_str})"
            else:
                raise TypeMappingError(f"No tuple type support for {target_lang}")

        # Handle custom types (records, variants)
        elif ir_type.kind in (TypeKind.CUSTOM, TypeKind.RECORD, TypeKind.VARIANT):
            # For custom types, check if there's a converter registered
            if ir_type.name in self._custom_converters:
                if target_lang in self._custom_converters[ir_type.name]:
                    converter = self._custom_converters[ir_type.name][target_lang]
                    return converter(ir_type)

            # Default: use the type name as-is (with some conventions)
            if target_lang == "python":
                # Python: CamelCase for classes
                return ir_type.name.title()
            elif target_lang == "c":
                # C: lowercase with _t suffix
                return f"{ir_type.name}_t"
            elif target_lang == "ocaml":
                # OCaml: lowercase
                return ir_type.name
            elif target_lang == "rust":
                # Rust: CamelCase
                return ir_type.name.title()
            else:
                return ir_type.name

        else:
            raise TypeMappingError(f"Unsupported type kind: {ir_type.kind}")

    def validate(self, ir_type: IRType, target_lang: str) -> bool:
        """
        Check if a type can be mapped to the target language.

        Args:
            ir_type: The IR type to validate
            target_lang: Target language

        Returns:
            True if mapping exists, False otherwise
        """
        try:
            self.get_mapping(ir_type, target_lang)
            return True
        except TypeMappingError:
            return False

Functions

register_primitive(ir_type_name, mappings)

Register a primitive type mapping.

Parameters:

Name Type Description Default
ir_type_name str

Name of the IR type (e.g., "string", "int")

required
mappings Dict[str, str]

Dictionary of language -> type_name mappings

required
Source code in src/polyglot_ffi/type_system/registry.py
def register_primitive(self, ir_type_name: str, mappings: Dict[str, str]) -> None:
    """
    Register a primitive type mapping.

    Args:
        ir_type_name: Name of the IR type (e.g., "string", "int")
        mappings: Dictionary of language -> type_name mappings
    """
    self._primitive_mappings[ir_type_name] = mappings
    self._mapping_cache.clear()  # Clear cache when registry is modified
register_converter(ir_type_name, target_lang, converter)

Register a custom type converter function.

Parameters:

Name Type Description Default
ir_type_name str

Name of the IR type

required
target_lang str

Target language

required
converter Callable[[IRType], str]

Function that takes IRType and returns type string

required
Source code in src/polyglot_ffi/type_system/registry.py
def register_converter(
    self, ir_type_name: str, target_lang: str, converter: Callable[[IRType], str]
) -> None:
    """
    Register a custom type converter function.

    Args:
        ir_type_name: Name of the IR type
        target_lang: Target language
        converter: Function that takes IRType and returns type string
    """
    if ir_type_name not in self._custom_converters:
        self._custom_converters[ir_type_name] = {}
    self._custom_converters[ir_type_name][target_lang] = converter
    self._mapping_cache.clear()  # Clear cache when registry is modified
get_mapping(ir_type, target_lang)

Get the type mapping for a target language.

Parameters:

Name Type Description Default
ir_type IRType

The IR type to map

required
target_lang str

Target language (e.g., "python", "c", "rust")

required

Returns:

Type Description
str

Type string in the target language

Raises:

Type Description
TypeMappingError

If no mapping exists

Source code in src/polyglot_ffi/type_system/registry.py
def get_mapping(self, ir_type: IRType, target_lang: str) -> str:
    """
    Get the type mapping for a target language.

    Args:
        ir_type: The IR type to map
        target_lang: Target language (e.g., "python", "c", "rust")

    Returns:
        Type string in the target language

    Raises:
        TypeMappingError: If no mapping exists
    """
    # Check cache first
    cache_key = (self._type_to_cache_key(ir_type), target_lang)
    if cache_key in self._mapping_cache:
        return self._mapping_cache[cache_key]

    # Compute the mapping
    result = self._compute_mapping(ir_type, target_lang)

    # Store in cache
    self._mapping_cache[cache_key] = result
    return result
validate(ir_type, target_lang)

Check if a type can be mapped to the target language.

Parameters:

Name Type Description Default
ir_type IRType

The IR type to validate

required
target_lang str

Target language

required

Returns:

Type Description
bool

True if mapping exists, False otherwise

Source code in src/polyglot_ffi/type_system/registry.py
def validate(self, ir_type: IRType, target_lang: str) -> bool:
    """
    Check if a type can be mapped to the target language.

    Args:
        ir_type: The IR type to validate
        target_lang: Target language

    Returns:
        True if mapping exists, False otherwise
    """
    try:
        self.get_mapping(ir_type, target_lang)
        return True
    except TypeMappingError:
        return False

Getting the Registry

from polyglot_ffi.type_system.registry import get_default_registry

# Get the global registry (lazy-initialized)
registry = get_default_registry()

Basic Usage

from polyglot_ffi.ir.types import STRING, INT, ir_option
from polyglot_ffi.type_system.registry import get_default_registry

registry = get_default_registry()

# Map primitive types
registry.get_mapping(STRING, "python")  # "str"
registry.get_mapping(INT, "python")     # "int"
registry.get_mapping(STRING, "rust")    # "String"
registry.get_mapping(INT, "c")          # "int64_t"

# Map complex types
opt_string = ir_option(STRING)
registry.get_mapping(opt_string, "python")  # "Optional[str]"
registry.get_mapping(opt_string, "rust")    # "Option<String>"

Built-in Type Mappings

The registry comes pre-loaded with standard type mappings:

String Types

IR Type Python Rust OCaml C
string str String string char*

Integer Types

IR Type Python Rust OCaml C
int int i64 int int64_t

Float Types

IR Type Python Rust OCaml C
float float f64 float double

Boolean Types

IR Type Python Rust OCaml C
bool bool bool bool int

Unit Types

IR Type Python Rust OCaml C
unit None () unit void

Complex Type Mappings

Option Types

from polyglot_ffi.ir.types import STRING, ir_option

opt_string = ir_option(STRING)

registry.get_mapping(opt_string, "python")  # "Optional[str]"
registry.get_mapping(opt_string, "rust")    # "Option<String>"
registry.get_mapping(opt_string, "ocaml")   # "string option"

List Types

from polyglot_ffi.ir.types import INT, ir_list

int_list = ir_list(INT)

registry.get_mapping(int_list, "python")  # "List[int]"
registry.get_mapping(int_list, "rust")    # "Vec<i64>"
registry.get_mapping(int_list, "ocaml")   # "int list"

Tuple Types

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

pair = ir_tuple(STRING, INT)

registry.get_mapping(pair, "python")  # "Tuple[str, int]"
registry.get_mapping(pair, "rust")    # "(String, i64)"
registry.get_mapping(pair, "ocaml")   # "(string * int)"

Nested Types

The type system handles arbitrarily nested types:

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

# option[list[tuple[str, int]]]
complex = ir_option(ir_list(ir_tuple(STRING, INT)))

registry.get_mapping(complex, "python")
# "Optional[List[Tuple[str, int]]]"

registry.get_mapping(complex, "rust")
# "Option<Vec<(String, i64)>>"

Custom Type Mappings

Registering Primitive Types

from polyglot_ffi.type_system.registry import TypeRegistry

registry = TypeRegistry()

# Register a custom primitive type
registry.register_primitive("bytes", {
    "ocaml": "bytes",
    "python": "bytes",
    "rust": "Vec<u8>",
    "c": "uint8_t*"
})

# Use it
from polyglot_ffi.ir.types import ir_primitive
bytes_type = ir_primitive("bytes")
registry.get_mapping(bytes_type, "python")  # "bytes"

Registering Type Converters

For complex custom types, register a converter function:

from polyglot_ffi.ir.types import IRType

def convert_timestamp(ir_type: IRType) -> str:
    """Convert timestamp type to Python."""
    return "datetime.datetime"

registry.register_converter("timestamp", "python", convert_timestamp)

# Use it
timestamp = ir_primitive("timestamp")
registry.get_mapping(timestamp, "python")  # "datetime.datetime"

Custom Types in Configuration

Users can define custom types in polyglot.toml:

[types.binary_data]
ocaml = "bytes"
python = "bytes"
rust = "Vec<u8>"
c = "uint8_t*"

[types.timestamp]
ocaml = "float"
python = "datetime.datetime"
rust = "SystemTime"
c = "time_t"

Type Validation

Check if a type mapping exists before using it:

from polyglot_ffi.ir.types import STRING, ir_primitive

registry = get_default_registry()

# Validate known type
is_valid = registry.validate(STRING, "python")  # True

# Validate unknown type
unknown = ir_primitive("unknown_type")
is_valid = registry.validate(unknown, "python")  # False

Error Handling

The registry raises TypeMappingError for invalid mappings:

from polyglot_ffi.type_system.registry import TypeMappingError
from polyglot_ffi.ir.types import ir_primitive

try:
    unknown = ir_primitive("unknown_type")
    registry.get_mapping(unknown, "python")
except TypeMappingError as e:
    print(f"Type mapping error: {e}")
    # "Unknown primitive type 'unknown_type'"

Performance

The type registry is optimized for speed:

  • O(1) primitive lookups: Direct dictionary access
  • Caching: Computed mappings cached automatically
  • Pre-compilation: No runtime regex compilation

Typical performance: - Primitive lookup: ~0.0003ms - Option lookup: ~0.0007ms (with cache hit) - Complex nested type: ~0.0019ms (with cache hit)

Cache Behavior

The registry automatically caches computed type mappings:

# First call: computes and caches
result1 = registry.get_mapping(ir_option(STRING), "python")

# Second call: returns cached result (faster)
result2 = registry.get_mapping(ir_option(STRING), "python")

# Cache is cleared when registry is modified
registry.register_primitive("newtype", {...})
# Cache cleared automatically

Usage in Generators

Generators use the registry to produce consistent type mappings:

from polyglot_ffi.ir.types import IRFunction, IRParameter
from polyglot_ffi.type_system.registry import get_default_registry

def generate_python_signature(func: IRFunction) -> str:
    """Generate Python function signature."""
    registry = get_default_registry()

    # Map parameter types
    params = []
    for param in func.parameters:
        py_type = registry.get_mapping(param.type, "python")
        params.append(f"{param.name}: {py_type}")

    # Map return type
    return_type = registry.get_mapping(func.return_type, "python")

    # Build signature
    params_str = ", ".join(params)
    return f"def {func.name}({params_str}) -> {return_type}:"

Complete Example

from polyglot_ffi.ir.types import (
    STRING, INT, BOOL, ir_option, ir_list, ir_tuple, ir_primitive
)
from polyglot_ffi.type_system.registry import (
    TypeRegistry, get_default_registry
)

# Get default registry with built-in types
registry = get_default_registry()

# Test primitive mappings
print("Primitives:")
for lang in ["python", "rust", "ocaml", "c"]:
    print(f"  {lang}: string -> {registry.get_mapping(STRING, lang)}")

# Test complex types
print("\nComplex types:")
opt_int = ir_option(INT)
print(f"  option[int] -> {registry.get_mapping(opt_int, 'python')}")

list_str = ir_list(STRING)
print(f"  list[str] -> {registry.get_mapping(list_str, 'rust')}")

pair = ir_tuple(STRING, INT)
print(f"  tuple[str,int] -> {registry.get_mapping(pair, 'python')}")

# Test nested types
nested = ir_option(ir_list(ir_tuple(STRING, INT)))
print(f"\nNested type:")
print(f"  Python: {registry.get_mapping(nested, 'python')}")
print(f"  Rust: {registry.get_mapping(nested, 'rust')}")

# Register custom type
registry.register_primitive("uuid", {
    "python": "uuid.UUID",
    "rust": "Uuid",
    "ocaml": "string",
    "c": "char*"
})

uuid_type = ir_primitive("uuid")
print(f"\nCustom type:")
print(f"  uuid -> {registry.get_mapping(uuid_type, 'python')}")

# Validate types
print(f"\nValidation:")
print(f"  STRING valid? {registry.validate(STRING, 'python')}")
print(f"  unknown valid? {registry.validate(ir_primitive('unknown'), 'python')}")

See Also