TYPED CONVERSIONS
Compose immutable records, read fields directly from function results, and process fixed local batches with explicit value semantics.
Bind a record to a local in the function body, a branch, or a supported loop. Every read requires an initialized value on every path that reaches it. Reassigning a local or record parameter is supported when the value retains the same exact class. 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. Targets may be fresh or existing names of the same exact record class. Swapping two initialized records with left, right = right, left preserves their original values while the targets are assigned.
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 may appear in a branch or loop, but cannot be quoted, redeclared or attached to attributes. Annotate a name once; use ordinary assignment when replacing its value later. 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.
Each branch can create a value of the same class. A branch that returns early does not need to initialize a value used only on the remaining paths. A missing else requires an earlier initialization if the record is read after the conditional. The check is conservative: it does not assume that a condition always succeeds or that a loop executes at least once.
class Point:
x: int
y: int
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def walk(start: int, steps: int, backwards: bool) -> int:
if backwards:
current: Point = Point(start, -1)
else:
current = Point(start, 1)
initial = current
for step in range(steps):
next_point = Point(current.x + current.y, current.y)
current = next_point
return initial.x + current.x
walk(10, 3, False) returns 23; walk(10, 3, True) returns 17. With zero steps it returns 20. Replacing current leaves initial with the original coordinates. The right-hand expression is evaluated completely before its value replaces the binding, so a constructor can safely read the previous value of that same name.
The same rules apply inside while loops, nested branches, and loop else clauses. A local first assigned inside a loop may be read within that iteration after its assignment. Reading it after the loop requires initialization on every exit, including zero iterations and every break. A value from one iteration is not assumed to exist at the start of another. Initialize a carried value before the loop, as current is above.
class Point:
x: int
y: int
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def select_point(limit: int) -> Point:
for index in range(limit):
selected = Point(index, 3)
break
else:
selected = Point(7, 8)
return selected
def adjust(point: Point, steps: int) -> Point:
for index in range(steps):
point = Point(point.x + 1, point.y)
return point
def example() -> int:
original = select_point(0)
changed = adjust(original, 2)
return original.x + changed.x
example() returns 16. In select_point, normal completion, including zero iterations, initializes the fallback; the only break initializes its result first. In adjust, replacing a record parameter leaves the caller's original value intact. continue skips the rest of its iteration, and inner-loop breaks affect only their own loop.
These are whole-value replacements. Field assignments such as point.x = 4 remain unsupported, including writes through an alias or a nested field. Record collection targets such as for point in points remain fresh, read-only names restricted to their loop body; copy the target into another local if you need to replace that local's value.
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.
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 local in any supported function region, retaining its exact class.
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.
| Feature | Typed contract |
|---|---|
| Record shape | 1–64 fields per class. Each field is exactly int, float, bool, or an earlier declared immutable record. |
| Nested layout | At 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 size | 1–64 records and at most 4096 expanded scalar leaves for the entire collection. No nested collections, dictionaries or sets of records. |
| Calls | Exact 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 bindings | Locals and record parameters may receive replacement values of the same exact class. Function, if/elif/else, for/while and loop-else regions are supported. Every read must have an initialized value on every continuing path. Optional exact class annotations need initial values; chained assignment and flat literal unpacking preserve evaluation order. At most 128 nested record control-flow regions. |
| Immutability | No field writes at any depth, collection element replacement, object identity, whole-record equality, truth tests or dynamic attributes. |
| Storage | Inline 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.