Functions
Functions are first-class names. Sema collects them before checking bodies so mutual recursion typechecks. Codegen declares LLVM functions, including extern "C" symbols and generic instantiations, then emits reachable bodies.
Shape
def scale(value: i32, factor: i32 = 2) -> i32:
return value * factor
def identity[T](value: T) -> T:
return value- Return type after
->may be omitted:maininfersi32,__init__infersvoid, other functions inferAny - Parameter types may be omitted (
Any) - Default arguments are allowed
- Generic type parameters:
[T]ondeforclass - Methods take
selfas the first parameter super()is the first base class:super().__init__(name),super().id()
There is no *args / **kwargs, no keyword-only parameters, and no nested def. Those are reserved and diagnose.
Inference is not gradual dynamism
Any is a top type, not “we will figure it out at runtime.” You can write untyped parameters and still compile, but the checker will not invent a precise type later. Prefer explicit i32 / str / T on anything that crosses a function boundary.
Lambdas
add1 = lambda (x: i32) -> i32: x + 1Untyped lambda x: ... parameters are Any. No capture. A lambda is a function value, not a closure over the enclosing frame. Pass what you need as arguments.
Native functions
extern "C" "native_add"
def add(left: i32, right: i32) -> i32The string is the link symbol. The def has no body. Codegen emits a declaration with that name. Link the object or lib with --link.
This is the typed path. The boxed Sere_Object API (Sere_DefineFunction) is the other path — see Interop.
Dunders as functions
If a type defines __len__, len(x) calls it. Same for __getitem__, __contains__, arithmetic, __enter__ / __exit__. The syntax is not special-cased per type in the parser; sema resolves the method.
main wrapping
User main is not the LLVM/C entry. The backend wraps it as C main, converts argv when the signature asks for list[str], then returns the i32 (or 0 for void). Module init runs first.