Agreed 2026-02-26. This is the iron-clad contract governing how constructors work in Aver.
Colon appears only in declarations: function parameters (x: Int), record fields (name: String), return types (-> Result<Int, String>). Never in expressions.
| Convention | Meaning | Examples |
|---|---|---|
lowerCamel or lower_snake |
Function name | parse, fromString, readAge |
UpperCamel |
Type / constructor / namespace | User, Shape.Circle, Result.Ok |
The parser uses the first character of the callee to distinguish function calls from constructor invocations. This is not a style guide — it is grammar.
Any call-site where the callee starts with an uppercase letter is a constructor. This includes:
- Bare names:
User(...) - Qualified names:
Shape.Circle(...),Result.Ok(...)
The final segment determines the kind: Map.fromList(...) is a function call (fromList is lower), Shape.Circle(...) is a constructor (Circle is upper).
Product types (records) are constructed with explicit field names:
record User
name: String
age: Int
u = User(name = "Alice", age = 30)
All fields are required exactly once. No defaults, no partial construction.
Variant constructors take positional arguments matching the type definition order:
type Shape
Circle(Float)
Rect(Float, Float)
Point
c = Shape.Circle(3.14)
r = Shape.Rect(2.0, 5.0)
A constructor with no parameters is a value, not a function call. No parentheses:
p = Shape.Point
n = Option.None
Rationale: Shape.Point is always the same value. Writing Shape.Point() would imply construction where there is none — like writing 42() to "construct" an integer.
Pattern matching is symmetric:
match shape
Shape.Circle(r) -> r * r * 3.14
Shape.Rect(w, h) -> w * h
Shape.Point -> 0.0
That symmetry is for constructors. Records are still data-only values: bind the whole record in a pattern and use field access by name. There is no positional record pattern like User(name, age).
This is not an independent rule — it follows automatically from rules 4 and 5:
- Records → always named (rule 4)
- Variants → always positional (rule 5)
There is no third kind of constructor. The parser sees = after the first argument name → record. No = → variant. One token of lookahead, zero ambiguity.
Dotted record constructors like Tcp.Connection(id = ..., host = ..., port = ...) are supported. Any Namespace.Type(...) form where the final segment is UpperCamel follows the same constructor rules as bare record types (rule 4: named arguments with =).
conn = Tcp.Connection(id = "tcp-1", host = "localhost", port = 8080)
User-defined types can be made opaque via exposes opaque [TypeName] in the module declaration. From outside the defining module, opaque types cannot be constructed, have fields accessed, or be pattern-matched. See language.md.
callee starts with UpperCamel?
├── NO → function call: f(args...)
└── YES → constructor
├── followed by `(`?
│ ├── YES → has arguments
│ │ ├── first arg is `Ident =`? → record create: User(name = "A", age = 1)
│ │ └── otherwise → variant create: Shape.Circle(3.14)
│ └── NO → zero-arg singleton: Option.None, Shape.Point
└── (opaque check: reject if type is non-constructable)
Expr::TypeAscription— removed (2026-02-26).:no longer appears in expressions. Typed bindings (name: Type = expr) reuse:in declaration position instead.- Ad-hoc lookahead heuristics for distinguishing records from function calls.