11. Generics
Type application uses angle brackets in type position:
list: List<i32>
m: Map<string, User>
The grammar is unambiguous because < opens a type-argument list only in type-parsing context - after :, ->, type, impl, struct, trait, def's generic-params slot, actor, and inside an existing <…> list. Elsewhere < and > are comparison operators; the expression parser never switches modes.
Inference rule
Type arguments are inferred from value arguments at the call site or from the surrounding type context (typed let, function-parameter type, return-type position of the enclosing function). There is no syntax for supplying type arguments inline to an expression - no parse<i32>("42"), no equivalent.
When inference cannot pin a type parameter, annotate the binding:
xs: List<i32> = List.empty() # the element type is pinned by the annotation
count = xs.length
The compiler emits a teaching diagnostic in two cases:
- A user writes
f<T>(args)orf::<T>(args)in expression position. The error says type arguments cannot be supplied inline and points at the typed-binding fix. - A let-binding without a type annotation leaves a type parameter unconstrained. The error says *cannot infer type for
<name>* and points at the same fix.
The cost: an API whose type parameter appears only in return position with no value parameter (empty<T>() -> List<T>, size_of<T>) needs either a value parameter or a typed binding at every call site. The benefit: a single, uniform syntax with no second-class escape hatch and no inline-type-argument commitment that locks <> out of any future alternative.
Single-uppercase-letter names are reserved for type parameters
A bare single uppercase letter in type position (T, K, V) is always an implicit type parameter - never a concrete type. A struct, type, or actor declared with a single-uppercase-letter name is therefore rejected at the declaration: such a name could never be referenced as itself. Give user-defined types a name of two or more characters (Point, Cmd, Tree).
The lowercase counterpart holds for effect rows: a bare single lowercase letter inside […] (e, r) is an effect variable (§6), never an effect name. Built-in and user effects are multi-letter (io, fs, Database) or capitalised (Crash), so the two never collide.