TYPED CONVERSIONS

Structured records

Compose immutable records, read fields directly from function results, and process fixed local batches with explicit value semantics.

Name and annotate local record values

Bind a record once to a fresh local directly in the function body. You can use an existing record name, a constructor or factory result, a nested field, or a fixed collection element. An optional local annotation must name the exact declaring class or a direct imported alias, and must include an initial value.

class Point:
    x: int
    y: int
    def __init__(self, x: int, y: int) -> None:
        self.x = x
        self.y = y

def make_point(x: int, y: int) -> Point:
    result: Point = Point(x, y)
    return result

def coordinate_total(x: int, y: int) -> int:
    original: Point = make_point(x, y)
    selected = original
    left = right = selected
    first, second = (left, Point(y, x))
    points = (first, second)
    last: Point = points[-1]
    return right.x + first.y + last.x + last.y

coordinate_total(3, 5) returns 16. Chained assignment evaluates its right-hand expression once. Flat unpacking accepts a matching list or tuple literal and evaluates every element from left to right before binding the new names. Each target must be fresh; swapping or rebinding existing record names is unsupported.

In Python, selected = original shares an object reference. Generated C stores a separate immutable value. Field reads remain equivalent because Typed conversion rejects field mutation, object identity, whole-record equality, truth tests and nested-scope capture. This does not add shared mutable objects. Passing or returning either name retains the exact class and inline value-copy interface.

Use item: Point = make_point(3, 5), rather than a declaration without a value. Local record annotations cannot be quoted, redeclared, attached to attributes, or placed inside branches or loops. A different class with identical fields is still a different type. Local annotations are conversion constraints; ordinary Python does not enforce these type hints. Aliases of the record collection itself remain unsupported.

Read fields on constructor and factory results

A field receiver may be a constructor, an understood record-returning function, a nested record field, or an indexed element of a fixed record collection. The receiver is evaluated once before the field is read. No temporary Python variable is required.

class Point:
    x: float
    y: float
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

def translate(p: Point, dx: float, dy: float) -> Point:
    return Point(p.x + dx, p.y + dy)

def shifted_x(x: float, y: float, dx: float) -> float:
    return translate(Point(x, y), dx, 0.0).x

shifted_x(2.0, 5.0, 3.0) returns 5.0. The constructor, call and field access preserve Python's evaluation order. A result used only in a selected branch is evaluated only in that branch.

Build nested immutable values

Declare the contained record before the class that uses it. Match the field and initializer annotations exactly, using a class name or a direct imported alias. The initializer still performs only the ordered copies self.field = field. Each nested field retains its declaring class identity.

class Point:
    x: float
    y: float
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

class Particle:
    position: Point
    velocity: Point
    mass: float
    def __init__(self, position: Point, velocity: Point, mass: float) -> None:
        self.position = position
        self.velocity = velocity
        self.mass = mass

def advance(p: Particle, dt: float) -> Particle:
    return Particle(
        Point(p.position.x + p.velocity.x * dt,
              p.position.y + p.velocity.y * dt),
        p.velocity,
        p.mass,
    )

def position_after(dt: float) -> float:
    initial = Particle(Point(1.0, 2.0), Point(3.0, -1.0), 2.0)
    return advance(initial, dt).position.x

position_after(0.5) returns 2.5. advance produces a new value containing a new position and a copy of the existing velocity. It cannot modify initial. A nested field may also be passed to a parameter, returned as its exact record type, or copied into a fresh local directly in the function body.

Process fixed local record collections

Use a nonempty list or tuple literal assigned once to a fresh local directly in the function body. Every element must have the same exact record class; constructor results, factory results, record locals, parameters and nested fields may supply the values. Elements are evaluated once from left to right, then copied into a fixed automatic C array.

class Point:
    x: float
    y: float
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

class Particle:
    position: Point
    velocity: Point
    mass: float
    def __init__(self, position: Point, velocity: Point, mass: float) -> None:
        self.position = position
        self.velocity = velocity
        self.mass = mass

def kinetic_energy(p: Particle) -> float:
    speed_squared = p.velocity.x * p.velocity.x + p.velocity.y * p.velocity.y
    return 0.5 * p.mass * speed_squared

def total_energy() -> float:
    particles = (
        Particle(Point(0.0, 0.0), Point(3.0, 4.0), 2.0),
        Particle(Point(1.0, 2.0), Point(0.0, 2.0), 3.0),
    )
    total = 0.0
    for particle in particles:
        total = total + kinetic_energy(particle)
    return total

def last_speed_squared() -> float:
    velocities = [Point(3.0, 4.0), Point(5.0, 12.0)]
    return velocities[-1].x * velocities[-1].x + velocities[-1].y * velocities[-1].y

total_energy() returns 31.0; last_speed_squared() returns 169.0. List and tuple syntax both use read-only record elements in this profile. A for loop visits elements in source order. Its fresh record target is usable only inside that loop's body; break, continue and a supported else clause retain their ordinary control-flow behavior.

Indexing requires an in-range integer literal, including a negative literal. Variable indices and slices are rejected before C generation. Collections cannot grow, change elements, be aliased, escape through a return, or cross function parameters. Pass individual records to functions instead.

Exact limits and representation

FeatureTyped contract
Record shape1–64 fields per class. Each field is exactly int, float, bool, or an earlier declared immutable record.
Nested layoutAt most 8 record levels, counting the outer class, and 256 scalar leaves per value. Repeated child fields each contribute their full leaf count. No recursive layouts, private/dunder field names or quoted/forward record annotations.
Collection size1–64 records and at most 4096 expanded scalar leaves for the entire collection. No nested collections, dictionaries or sets of records.
CallsExact positional constructor and record-boundary arguments. No record defaults, keyword-only parameters, variadics or recursive functions. Identical layouts do not make different classes interchangeable.
Local bindingsFresh single-assignment names, optional exact class annotations with initial values, chained assignment and flat literal unpacking. Direct function-body scope only.
ImmutabilityNo field writes at any depth, element replacement, record rebinding, object identity, whole-record equality, truth tests or dynamic attributes.
StorageInline structs and automatic arrays, copied by value. No heap allocation or caller cleanup. Large nested values and batches use more stack space and may involve more copying.

Record constructors and calls that pass or return records are unsupported inside and/or or chained comparisons. Use explicit branches, or read the scalar field into a local first. Ordinary arithmetic and single comparisons can read fields directly from supported record results. Inheritance, decorators, methods beyond the exact initializer, and string/container fields require other supported conversion forms; see Runtime classes for the separate Runtime contract.

For multiple files, directly import each record from its defining supplied module before any class or function annotation that uses it. The same type layouts appear in the shared header and manifest of separate C module exports. Regenerate and rebuild C callers when a record layout changes. Integer arithmetic must remain within the documented signed 64-bit bounds; floating operations use the documented C double representation.