Type Contracts
Nilakan type hints are persistent contracts. Known shapes are checked statically, and unknown shapes are checked recursively at runtime when assignments, parameters, and returns are evaluated.
Syntax
Section titled “Syntax”x: num = 1name: str = "nilakan"ok: bool = Truexs: list[num] = [1, 2, 3]pair: tuple[str, num] = ("a", 1)lookup: dict[str, num] = {"a": 1}value: num | str = "unknown"fallible: num | err = [1].get(10)Semantics
Section titled “Semantics”Supported scalar type names are num, str, bool, and err.
Supported container type names are list, tuple, set, and dict.
Container contracts can be bare or parameterized:
list[T]set[T]dict[K, V]tuple[T1, T2, ...]
Tuple contracts with arguments are fixed-size positional contracts. Unions use the pipe operator.
Contracts are checked for:
- top-level assignments
- function arguments
- function return values
initstate assignments- whole primed updates
- sparse updates into typed containers
def bump(xs: list[num]) -> list[num]: init: values: list[num] = xs done: bool = False while not done: values'[0] = values[0] + 1 done' = True return valuesConstraints And Errors
Section titled “Constraints And Errors”Unknown type names are rejected. Scalar types do not accept type arguments. list and set accept
zero or one type argument. dict accepts zero or two type arguments. A union must contain at least
two distinct options.
Duplicate union options are deduplicated. A union that collapses to one distinct option is invalid.
Deep container contracts are checked recursively.
table: dict[str, list[tuple[num, bool]]] = { "rows": [(1, True), (2, False)]}Direct-error permission is intentionally shallow. Exact root err requires a direct error, and a
root union containing err accepts one. Other roots—including no annotation—reject direct errors. A
nested err permits errors only in that annotated container position.
def first(xs: list[num]) -> num | err: return try xs.get(0)
bad: err = [].get(0)mixed: list[num | err] = [1, bad]Annotations persist across every later write. A current explicit annotation validates the value and replaces the previous contract; otherwise the existing contract applies. Assignment, destructuring, subscript assignment, primed updates, and commit-loop state use the same rule.
Replacing a contract through reannotation can weaken it. This is current behavior, not recommended style; use one annotation for a binding and preserve that contract across later assignments. An explicit conversion or rebinding design remains open work.
Known recursive incompatibility is NLK2011. Its diagnostic identifies the structural path and the
expected and inferred types when available, such as
dictionary value -> list element -> tuple item 2: expected bool, got str. Unknown shapes defer to
authoritative recursive runtime validation.
Examples
Section titled “Examples”def first_pair(xs: list[num]) -> tuple[num, num]: return xs[0], xs[1]
left, right = first_pair([3, 4])print(left + right)