# OriLang — Complete Language Reference (llms-full.txt) > The full OriLang specification, grammar, and operator rules in one document, followed by real example programs from the conformance test suite. Index version of this file: https://ori-lang.com/llms.txt --- # Ori Syntax Quick Reference > Condensed syntax and prelude reference for the Ori programming language, generated from the compiler team's maintained cheat sheet. The specification (https://raw.githubusercontent.com/upstat-io/ori-lang/master/docs/ori_lang/v2026/spec/) and grammar.ebnf are authoritative. **Syntax family: Rust-like.** Statements terminate with `;` (including every `let` in a block); the closing `}` of an expression-bodied declaration or block-ending-statement is the only place `;` is omitted. Default to `;` — ML/Haskell/Gleam newline-as-terminator instincts do NOT apply. ## Declarations **Functions**: `@name (p: T) -> R = expr;` | `@name (p: T) -> R = { ... }` (no `;`) | `pub @name` | `@name` | `@name` | `@name` | `where T: Clone` | `uses Capability` | `(x: int = 10)` defaults **Variadics**: `@sum (nums: ...int) -> int` | receives as `[T]` | call: `sum(1, 2, 3)` | spread: `sum(...list)` | trait objects: `...Printable` | empty calls need explicit type for generics **Clauses**: `@f (0: int) -> int = 1` then `@f (n) = n * f(n-1)` | `if guard` | exhaustive, top-to-bottom **Constants**: `let $name = value;` | `pub let $name` | module-level must be `$` **Const Functions**: `$name (p: T) -> R = expr` — pure, comptime, limits: 1M steps/1000 depth/100MB/10s **Types**: `type N = { f: T }` struct | `A | B | C(f: T)` sum | `type N = Existing` newtype | `type N` | `#derive(Eq)` | `pub type` **Newtypes**: `type UserId = int` | construct: `UserId(42)` | `.inner` (always public) | no trait/method inheritance | `#derive(Eq, Clone)` required | zero cost **Traits**: `trait N { @m (self) -> T }` | `@m (self) -> T = default` | `type Item` assoc | `type Output = Self` default | `trait C: P` | `@m () -> Self` assoc fn | `trait N` default type param **Impls**: `impl T { @m }` inherent | `impl T: Trait` | `impl C: Trait` | `self` (mutable in methods — mutations propagate to caller) / `Self` **Method generics**: `impl Box { @map (self, f: T -> U) -> Box = ... }` | `` bounded | `where U: Trait` clause | duplicate binder `` rejected (`E1002`) | def-impl methods may not declare `uses` (stateless contract) | non-object-safe traits cannot include methods with `<...>` (object-safety §Object Safety) **Associated Functions**: `impl T { @new () -> T }` — no `self` | call: `Type.method()` | `Self` in return | generics: `Option.some(v:)` **Default Impls**: `pub def impl Trait { @m }` — stateless, one per trait/module, auto-bound, override with `with` **Extensions**: `extend Type { @m (self) -> T }` | `extend [T]` | `extend T where T: Bound` | `pub extend` | no statics/fields/override **Resolution**: Diamond=single impl; Inherent>Trait>Extension; qualified: `Trait.method(v)`, `Type::Trait::Assoc`; extensions: `module.Type.method(v)` **Object Safety**: No `Self` return/param (except receiver), no generic methods; safe: `Printable`, `Debug`, `Hashable`; unsafe: `Clone`, `Eq`, `Iterator` **Tests**: `@t tests @fn () -> void` | `@name tests _ () -> void` floating | `tests @a tests @b` multi | `#skip("r")` | `#compile_fail("e")` | `#fail("e")` ## Conditional Compilation **Target**: `#target(os: "linux")` | `arch:` | `family:` | `any_os:` | `not_os:` | file-level: `#!target(...)` **Config**: `#cfg(debug)` | `release` | `feature:` | `any_feature:` | `not_debug` | `not_feature:` **Constants**: `$target_os`, `$target_arch`, `$target_family`, `$debug`, `$release` — false branch not type-checked ## Types **Primitives**: `int` (i64), `float` (f64), `bool`, `str` (UTF-8), `char`, `byte`, `void`, `Never` **Special**: `Duration` (`100ns`/`us`/`ms`/`s`/`m`/`h`), `Size` (`100b`/`kb`/`mb`/`gb`/`tb`) **Collections**: `[T]` list, `[T, max N]` fixed-capacity, `{K: V}` map, `Set` **Compound**: `(T, U)` tuple (access: `.0`, `.1`), `()` unit, `(T) -> U` fn, `Trait` object, `impl Trait` existential **Generic**: `Option`, `Result`, `Range`, `Ordering` **Const Generics**: `$N: int` | `@f` | `$B: bool` | `where N > 0` | `where N > 0 && N <= 100` **Const Bounds**: comparison (`==`/`!=`/`<`/`<=`/`>`/`>=`), logical (`&&`/`||`/`!`), arithmetic (`+`/`-`/`*`/`/`/`%`), bitwise (`&`/`|`/`^`/`<<`/`>>`) | multiple `where` = AND **Channels**: `Producer`, `Consumer`, `CloneableProducer`, `CloneableConsumer` (`T: Sendable`) **Concurrency**: `Nursery`, `NurseryErrorMode` (`CancelRemaining | CollectAll | FailFast`) **FFI**: `CPtr` (C opaque), `JsValue` (JS handle), `JsPromise` (JS async) **Rules**: No implicit conversions; overflow panics; `str[i]` → single-codepoint `str` ### Duration & Size **Duration**: 64-bit nanoseconds; suffixes `ns`/`us`/`ms`/`s`/`m`/`h`; decimal syntax (`0.5s`=500ms, `1.5s`=1500ms) **Size**: 64-bit bytes (non-negative); suffixes `b`/`kb`/`mb`/`gb`/`tb`; SI units (1000-based); decimal syntax (`1.5kb`=1500 bytes) **Decimal literals**: Compile-time sugar using integer arithmetic (no floats); must result in whole base unit; `1.5ns`/`0.5b` = error **Arithmetic**: `+`/`-`/`*`/`/`/`%`, unary `-` (Duration only; Size `-` panics if negative, unary `-` = compile error) **Methods**: `.nanoseconds()`/`.microseconds()`/`.milliseconds()`/`.seconds()`/`.minutes()`/`.hours()` | `.bytes()`/`.kilobytes()`/`.megabytes()`/`.gigabytes()`/`.terabytes()` → `int` **Factory**: `Duration.from_nanoseconds(ns:)`... | `Size.from_bytes(b:)`... **Traits**: `Eq`, `Comparable`, `Hashable`, `Clone`, `Debug`, `Printable`, `Default` (`0ns`/`0b`), `Sendable` ### Never Bottom type (uninhabited); coerces to any `T` **Producers**: `panic(msg:)`, `todo()`, `unreachable()`, `break`, `continue`, `expr?` on Err/None, infinite `loop` **Generics**: `Result` = always Err | `Result` = always Ok | `Option` = always None **Restrictions**: Cannot be struct field; may be sum variant payload (unconstructable) ### Fixed-Capacity Lists `[T, max N]` — inline-allocated, compile-time max N, dynamic length 0..N | `[T, max N] <: [T]` **Methods**: `.capacity()`, `.is_full()`, `.remaining()`, `.push()` (panics), `.try_push()` → `bool`, `.push_or_drop()`, `.push_or_oldest()`, `.to_dynamic()` **Conversion**: `.to_fixed<$N>()` panics | `.try_to_fixed<$N>()` → `Option` ### Existential Types (`impl Trait`) `impl Trait where Assoc == Type` — opaque return type; concrete type hidden from callers **Position**: return only | argument position: use generics instead **Syntax**: `@f () -> impl Iterator where Item == int` | `impl A + B` multi-trait **Where clause**: type-local (constraints on associated types, not type params) **Dispatch**: static (monomorphized) — no vtable overhead **Rules**: all return paths must yield same concrete type **vs Trait objects**: `impl Trait` (static/single type) vs `Trait` (dynamic/any type at runtime) ## Literals `42`, `1_000_000`, `0xFF`, `0b1010` | `3.14`, `2.5e-8` | `"hello"` (escapes: `\\\"\n\t\r\0\xHH\u{H}`) | `` `{name}` `` | `'a'`, `'\x41'` (escapes: `\\\'\n\t\r\0\xHH\u{H}`, `\xHH` restricted to `\x00`–`\x7F`) | `b'x'`, `b'\xFF'` (byte literal, escapes: `\\\'\n\t\r\0\xHH`, full `\x00`–`\xFF`, no `\u{}` or `\"`) | `true`/`false` | duration/size literals | `[1, 2]`, `[...a, ...b]` | `{key: v}`, `{"key": v}`, `{[expr]: v}`, `{...a, ...b}` | `Point { x, y }`, `{ ...p, x: 10 }` ## Operators (precedence high→low) 1. `.` `[]` `()` `?` `as` `as?` — 2. `**` (right) — 3. `!` `-` `~` — 4. `*` `/` `%` `div` `@` — 5. `+` `-` — 6. `<<` `>>` — 7. `..` `..=` (with optional `by` step modifier) — 8. `<` `>` `<=` `>=` — 9. `==` `!=` — 10. `&` — 11. `^` — 12. `|` — 13. `&&` — 14. `||` — 15. `??` (right) — 16. `|>` (pipe) **Unary**: `!` (Not), `-` (Neg), `~` (BitNot) | **Bitwise**: `&`/`|`/`^` (BitAnd/Or/Xor), `<<`/`>>` (Shl/Shr) **Shift overflow**: negative count panics; count ≥ bit width panics; `1 << 63` panics **Operator traits**: desugar to trait methods; user types implement for operator support **Compound assignment**: `x op= y` desugars to `x = x op y` (parser-level) | `+=` `-=` `*=` `/=` `%=` `**=` `@=` `&=` `|=` `^=` `<<=` `>>=` `&&=` `||=` | statement, not expression | target must be mutable (no `$`) | `&&=`/`||=` preserve short-circuit **Pipe**: `x |> f(a: v)` fills single unspecified param | `x |> .method()` calls method on piped value | `x |> (a -> expr)` lambda fallback | prec 16 (lowest) | left-assoc | desugars to let-binding + call in type checker | "unspecified" = no value AND no default ## Expressions **Conditionals**: `if c then e else e` | `if c then e` (void) **Bindings**: `let x = v` mutable | `let $x` immutable | `let x: T` | shadowing OK | `let { x, y }` | `let { x: px }` | `let (a, b)` | `let [..t]` / `let [..$t]` rest-only (irrefutable) — refutable list shapes (`[a, b, c]`, `[head, ..tail]`, `[only]`) are illegal in `let` (E2001), bind via `match` (per 15-patterns.md) **Indexing**: `list[0]`, `list[# - 1]` (`#`=length, panics OOB) | `map["k"]` → `Option` **Index/Field Assignment**: `list[i] = x` → `list = list.updated(key: i, value: x)` | `state.field = x` → `state = { ...state, field: x }` | mixed chains: `state.items[i] = x`, `list[i].name = x` | compound: `list[i] += 1` | root must be mutable (non-`$`) **Access**: `v.field`, `v.0` (tuple), `v.method(arg: v)` — named args required except: fn variables, single-param with inline lambda **Argument Punning**: `f(x:)` = `f(x: x)` when variable matches param name | `f(x:, y: 42)` mixed | trailing `:` distinguishes from positional `f(x)` **Lambdas**: `x -> x + 1` | `(a, b) -> a + b` | `() -> 42` | `(x: int) -> x * 2` (typed param, inferred return) | `(x: int) -> int = x * 2` (explicit return) — capture by value **Ranges**: `0..10` excl | `0..=10` incl | `0..10 by 2` | descending: `10..0 by -1` | infinite: `0..`, `0.. by -1` | int only **Blocks**: `{ let $x = 1; x + 2 }` — `;` terminates statements, last expression (no `;`) is value | all `;` = void block | `ori fmt` enforces blank line before result | empty `{ }` = empty map **Loops**: `while c do e` | `for i in items do e` | `for x in items yield x * 2` | `for x in items if g yield x` | nested `for` | `loop { body }` + `break`/`continue` | `break value` | `continue value` **While**: `while condition do body` — sugar for `loop { if !condition then break; body }` | type: `void` | no `while...yield` | `break value` error (E0860) | `continue value` error (E0861) **Loop body**: block expression; `loop { a \n b \n c }` for sequences | type: `void` (break no value), inferred (break value), `Never` (no break) | `continue value` error (E0861) **Yield control**: `continue` skips | `continue value` substitutes | `break` stops | `break value` adds final | `{K: V}` from `(K, V)` tuples **Labels**: `loop:name` | `for:name` | `while:name` | `block:name` | `break:name` | `continue:name` | no shadowing | `continue:name value` in yield → outer **Labeled blocks**: `block:name { body }` — early exit via `break:name value` | type = unified exit paths | bare `break` is loop-only (not block) | `continue:block_label` = error | transparent to `break:loop_label`/`continue:loop_label` **Spread**: `[...a, ...b]` | `{...a, ...b}` | `P { ...orig, x: 10 }` — later wins, literal contexts only | `fn(...list)` into variadic only ## Block expressions **Semicolons**: Rust-style — `;` terminates statements; last expression (no `;`) is block value; all `;` = void block **Semicolon rule**: Ends with `}`? No `;`. Everything else: `;`. Applies to `use`, `let $`, functions, types, methods. **Blocks**: `{ let $x = 1; let $y = 2; x + y }` — `;` on statements, no `;` on result **Match**: `match expr { P1 -> e1, P2 -> e2 }` — scrutinee before block, comma-separated arms (trailing comma optional) **Try**: `try { let $x = f()?; Ok(x) }` — error-propagating block **Contracts**: `pre(condition)` | `pre(condition | "message")` | `post(r -> condition)` — on function declaration, between signature and `=` **function_exp**: `recurse(condition:, base:, step:, memo:, parallel:)` | `parallel(tasks:, max_concurrent:, timeout:)` → `[Result]` | `spawn(tasks:, max_concurrent:)` → `void` | `timeout(op:, after:)` | `cache(key:, op:, ttl:)` | `with(acquire:, action:, release:)` | `for(over:, match:, default:)` | `catch(expr:)` → `Result` | `nursery(body:, on_error:, timeout:)` **Channels**: `channel(buffer:)` → `(Producer, Consumer)` | `channel_in` | `channel_out` | `channel_all` **Conversions**: `42 as float` infallible | `"42" as? int` fallible → `Option` **Match patterns**: literal | `x` | `_` | `Some(x)` | `{ x, y }` | `[a, ..rest]` | `1..10` | `A | B` | `x @ pat` | `x if guard` | variant punning: `Circle(radius:)` = `Circle(radius: radius)`, `Some(value:)` = `Some(value: value)` **Exhaustiveness**: match exhaustive; guards need `_`; `let` patterns irrefutable ## Imports **Relative**: `use "./math" { add };` | `"../utils"` | `"./http/client"` **Module**: `use std.math { sqrt };` | `use std.net.http as http;` **Private**: `use "./m" { ::internal };` | **Alias**: `{ add as plus }` | **Re-export**: `pub use` **Without default**: `use "m" { Trait without def };` — import without `def impl` **Extensions**: `extension std.iter.extensions { Iterator.count }` — method-level, no wildcards | `pub extension` ## FFI **Native (C)**: `extern "c" from "lib" { @_sin (x: float) -> float as "sin" }` | `from` specifies library | `as` maps name **JavaScript**: `extern "js" { @_sin (x: float) -> float as "Math.sin" }` | `extern "js" from "./utils.js"` **C Variadics**: `extern "c" { @printf (fmt: CPtr, ...) -> c_int }` — untyped, requires `unsafe`, platform va_list ABI **Types**: `CPtr` opaque | `Option` nullable | `JsValue` handle | `JsPromise` async **C Types**: `c_char`, `c_short`, `c_int`, `c_long`, `c_longlong`, `c_float`, `c_double`, `c_size` **Layout**: `#repr("c")` C-compatible | `#repr("packed")` no padding | `#repr("transparent")` same as single field | `#repr("aligned", N)` minimum alignment (power of two) | struct types only; newtypes implicitly transparent **Unsafe**: `unsafe { ptr_read(...) }` | **Capability**: `uses Unsafe` (marker, like `Suspend` — cannot be bound via `with...in`) **Async WASM**: `JsPromise` implicitly resolved at binding sites | **Compile Error**: `compile_error("msg")` ### Deep FFI (opt-in annotations on extern blocks) **Error Protocols**: `extern "c" from "lib" #error(errno) { ... }` — block-level; `#error(none)` per-function opt-out **Error Variants**: `#error(errno | nonzero | null | negative | success: N | none)` — auto-generates `Result` **FfiError**: `use std.ffi { FfiError }` — `{ code: int, message: str, source: str }` **Out Params**: `@f (name: str, db: out CPtr) -> c_int` — `out` params folded into return type **Ownership**: `owned` / `borrowed` on params/returns | str returns default to `borrowed` (copy, don't free) **Free**: `#free(fn)` on block or per-function — auto-generates `Drop` impl for `owned CPtr` **[byte] Elision**: `[byte]` in extern generates adjacent `(ptr, len)` C args | `mut [byte]` generates `(ptr, &len)` **Parametric FFI**: `uses FFI("sqlite3")` per-library | `uses FFI` shorthand for all | each `from "lib"` is distinct capability **Mocking**: `with FFI("lib") = handler { fn: (...) -> T = ..., } in { ... }` — handler-based mock; stateless is sugar for `handler(state: ())` ## Capabilities **Declare**: `@f (...) -> T uses Http = ...` | `uses FileSystem, Suspend` **Provide**: `with Http = RealHttp { } in expr` | `with Http = mock, Cache = mock in expr` **Stateful Handlers**: `with Cap = handler(state: init) { op: (s) -> (s', val), ... } in expr` — state replaces `self`; returns `(S, R)` tuple; frame-local mutable state; `with...in` returns body type only **Handler Rules**: context-sensitive keyword; single state value (compose via structs); all trait methods required (defaults used if omitted); no `self`; errors E1204-E1207 **Resolution**: with...in > imported `def impl` > module-local `def impl` **Suspend**: `uses Suspend` = may suspend; no `uses` = sync; concurrency via `parallel(...)` **Standard**: `Http`, `FileSystem`, `Clock`, `Random`, `Crypto`, `Cache`, `Print` (default), `Logger`, `Env`, `Intrinsics`, `Suspend`, `FFI` **Intrinsics**: Generic SIMD/bit ops; `Intrinsics.simd_add(a:, b:)` (monomorphized by `[T, max N]`), `count_ones(value:)`, `cpu_has_feature(feature:)`; comparisons return `Mask<$N>` (methods: `bits`, `any`, `all`, `count`, `first_set`; operators: `&`, `|`, `~`) **Capsets**: `capset Net = Http, Dns, Tls` — transparent alias, expanded in `uses` before type checking; `@f uses Net` expands to `@f uses Http, Dns, Tls`; capsets can include other capsets; not a trait (no `impl`, no `with`, no `def impl`) ## Comments `// comment` — own line only | Doc: `// Desc` | `// * name:` | `// ! Error:` | `// > expr -> result` ## Formatting 4 spaces, 100 char limit, trailing commas multi-line only | `;` terminates statements in blocks, `use`, `let $`, expression-bodied declarations; block body `}` = no `;` | Space around: binary ops, arrows, colons/commas, `pub`, all braces `{ }`, `as`/`by`/`|`/`with`/`+`, `=` in ``, `??`, compound `+=` | No space: parens/brackets, `.`/`..`/`?`/`...`, empty delimiters, before `;`, labels `:`, punning `name:` | Break at 100; blocks 4-space indent; blank line before result in setup+result blocks; `match`/`try`/`recurse`/`parallel`/`spawn`/`nursery` always stacked; `timeout`/`cache`/`catch` width-based | Params/args/generics/where/fields/variants one-per-line; chains break at `.method()` (all-or-nothing); binary break before op; `if...then` together, `else` newline; chained `else if` each on own line; `for...yield`/`do` inline if fits | File order: file attrs → imports (stdlib→relative, sorted alpha) → constants → user-ordered rest | Attrs canonical order: `#target`/`#cfg` → `#repr` → `#derive` → `#skip`/`#compile_fail`/`#fail` | Traits: assoc types → required methods → defaults | Impls: assoc types → methods in trait order | Parens always preserved; never removed by formatter ## Keywords **Reserved (36)**: `as break continue def div do else extend extension extern false for if impl in let loop match Never pub self Self suspend tests then trait true type unsafe use uses void where while with yield` **Reserved (future)**: `asm inline static union view` (reserved for future low-level features) **Context-sensitive (type names, 5)**: `bool byte float int str` **Context-sensitive (patterns, 9)**: `cache catch handler nursery parallel recurse spawn timeout try` **Context-sensitive (blocks, 1)**: `block` (before `:` only — labeled block early exit) **Context-sensitive (pattern args, 10)**: `body buffer default expr map on_error over pre post state` **Context-sensitive (imports, 2)**: `from` (extern blocks), `without` (import items, before `def`) **Context-sensitive (other, 3)**: `args` (@main params), `by` (after range), `max` (fixed-capacity lists) **Context-sensitive (embed, 2)**: `embed` (file embedding), `has_embed` (file existence check) **Built-in constructors (4)**: `channel channel_all channel_in channel_out` **Built-in names**: `len is_empty is_some is_none is_ok is_err compare min max print panic todo unreachable dbg compile_error hash_combine repeat is_cancelled drop_early` ## Prelude **Types**: `Option` (`Some`/`None`), `Result` (`Ok`/`Err`), `Error`, `TraceEntry`, `Ordering`, `PanicInfo`, `CancellationError`, `CancellationReason`, `FormatSpec`, `Alignment`, `Sign`, `FormatType` **Traits**: `Eq`, `Comparable`, `Hashable`, `Printable`, `Formattable`, `Debug`, `Clone`, `Default`, `Drop`, `Len`, `IsEmpty`, `Iterator`, `DoubleEndedIterator`, `Iterable`, `Collect`, `Into`, `Traceable`, `Index`, `Sendable`, `Value` **Built-ins**: `print(msg:)`, `len(collection:)`, `is_empty(collection:)`, `is_some/is_none(option:)`, `is_ok/is_err(result:)`, `panic(msg:)`→`Never`, `todo()`/`todo(reason:)`→`Never`, `unreachable()`/`unreachable(reason:)`→`Never`, `dbg(value:)`/`dbg(value:, label:)`→`T`, `compare(left:, right:)`→`Ordering`, `min/max(left:, right:)`, `hash_combine(seed:, value:)`→`int`, `repeat(value:)`→iter (`T: Clone`), `is_cancelled()`→`bool`, `compile_error(msg:)`, `drop_early(value:)`, `embed(path)`→type-driven (`str`/`[byte]`), `has_embed(path)`→`bool` **Testing (`std.testing`, import required)**: `assert(cond:)`, `assert_eq(actual:, expected:)`, `assert_ne(actual:, unexpected:)`, `assert_some/none(opt:)`, `assert_ok/err(result:)`, `assert_panics(f:)`, `assert_panics_with(f:, msg:)`. Import with `use std.testing { assert, assert_eq }`. **Option**: `.map(transform:)`, `.unwrap()`, `.unwrap_or(default:)`, `.expect(msg:)`, `.ok_or(err:)`, `.and_then(then:)`, `.flat_map(f:)`, `.or(alt:)`, `.filter(predicate:)` **Result**: `.map(transform:)`, `.map_err(transform:)`, `.unwrap()`, `.unwrap_err()`, `.unwrap_or(default:)`, `.expect(msg:)`, `.expect_err(msg:)`, `.ok()`, `.err()`, `.and_then(then:)`, `.or_else(f:)`, `.trace()`→`str`, `.trace_entries()`→`[TraceEntry]`, `.has_trace()` **Error**: `.trace()`, `.trace_entries()`, `.has_trace()` **Ordering**: `Less | Equal | Greater` — `.is_less/equal/greater()`, `.is_less_or_equal/greater_or_equal()`, `.reverse()`, `.then(other:)`, `.then_with(f:)`; default `Equal`; order `Less < Equal < Greater`; impls Eq, Comparable, Clone, Debug, Printable, Hashable, Default **Printable**: `@to_str (self) -> str` — required for `` `{x}` ``; all primitives impl **Formattable**: `@format (self, spec: FormatSpec) -> str` — blanket for Printable; spec: `[[fill]align][sign][#][0][width][.precision][type]`; align `<>^`; sign `+ - `; types `bxXoeEf%`; `#` prefix; `0` pads **Debug**: `@debug (self) -> str` — escaped strings, derivable | **Clone**: `@clone (self) -> Self` — all primitives/collections, derivable **Iterator**: `type Item; @next (self) -> (Option, Self)` — fused, copy elision, lazy **DoubleEndedIterator**: `trait: Iterator { @next_back (self) -> (Option, Self) }` **Iterable**: `type Item; @iter (self) -> impl Iterator` | **Collect**: `@from_iter (iter: impl Iterator) -> Self` **Iterator methods**: `.map`, `.filter`, `.fold`, `.find`, `.for_each`, `.collect`, `.count`, `.any`, `.all`, `.take`, `.skip`, `.enumerate`, `.zip`, `.chain`, `.flatten`, `.flat_map`, `.cycle`, `.join` **DoubleEnded methods**: `.rev`, `.last`, `.rfind`, `.rfold` **Infinite**: `repeat(value:)`, `(0..).iter()` — bound with `.take(count:)` before `.collect()` **Into**: `@into (self) -> T` — lossless, explicit `.into()`, standard: str→Error, int→float, Set→[T]; no identity/chaining **Traceable**: `@with_trace`, `@trace`→`str`, `@trace_entries`→`[TraceEntry]`, `@has_trace` **TraceEntry**: `{ function, file, line, column: int }` — `@` prefix; most recent first **PanicInfo**: `{ message, location: TraceEntry, stack_trace: [TraceEntry], thread_id: Option }` **Drop**: `@drop (self) -> void` — refcount zero; not async; panic during unwind aborts **Index**: `@index (self, key: Key) -> Value` — `x[k]`→`x.index(key: k)`; return `T`/`Option`/`Result`; `#` built-in only; multiple impls per type OK: `impl Index` + `impl Index` disambiguated by key type at compile time **Eq**: `@equals (self, other: Self) -> bool` — reflexive/symmetric/transitive; derives `==`/`!=` **Comparable**: `trait: Eq { @compare (self, other: Self) -> Ordering }` — total order; derives `<`/`<=`/`>`/`>=`; NaN > all; `None < Some`; `Ok < Err` **Hashable**: `trait: Eq { @hash (self) -> int }` — `a == b` ⇒ same hash; +0.0/-0.0 same; use `hash_combine` **Operator traits**: `Add`/`Sub`/`Mul`/`Div`/`FloorDiv`/`Rem`/`Pow` — binary; `MatMul` — matrix multiply (`@`); `Neg`/`Not`/`BitNot` — unary; `BitAnd`/`BitOr`/`BitXor`, `Shl`/`Shr` — bitwise; `As`/`TryAs` — conversion (`as`/`as?`); all default `type Output = Self` **Operator methods**: `add`/`subtract`/`multiply`/`divide`/`floor_divide`/`remainder`/`power` — arithmetic; `matrix_multiply` — matmul (`@`); `negate`/`not`/`bit_not` — unary; `bit_and`/`bit_or`/`bit_xor`/`shift_left`/`shift_right` — bitwise; `as`/`try_as` — conversion **Sendable**: marker trait, auto-derived by compiler; all fields must be `Sendable`, no interior mutability, no non-Sendable captures; required for channel types `T: Sendable`; cannot be implemented manually **Value**: `trait Value: Clone, Eq` — marker trait; inline storage, bitwise copy, no ARC, no Drop; all fields must be `Value`; cannot be implemented manually; auto-satisfies `Clone` + `Sendable`; warning >256 bytes, error >512 bytes; primitives (`int`/`float`/`bool`/`char`/`byte`/`void`/`Duration`/`Size`/`Ordering`) implicitly `Value`; `str`/`[T]`/`{K:V}`/`Set` never `Value`; syntax: `type Point: Value, Eq = { x: float, y: float }` **List methods**: `.map`/`.filter`/`.fold(initial:, op:)`/`.reduce(op:)`/`.find(where:)`/`.any(pred:)`/`.all(pred:)`/`.for_each(f:)`/`.flat_map(f:)`/`.flatten()`/`.first()`/`.last()`/`.get(index:)`/`.take(count:)`/`.take_while(pred:)`/`.skip(count:)`/`.skip_while(pred:)`/`.slice(start:, end:)`/`.chunk(size:)`/`.window(size:)`/`.enumerate()`/`.zip(other:)`/`.concat(other:)`/`.append(value:)`/`.prepend(value:)`/`.push(value:)`/`.pop()`/`.insert(index:, value:)`/`.remove(index:)`/`.set(index:, value:)`/`.reverse()`/`.sort()`/`.sort_by(cmp:)`/`.sort_stable()`/`.sorted()`/`.unique()`/`.partition(pred:)`/`.group_by(key:)`/`.count()`/`.min()`/`.max()`/`.min_by(key:)`/`.max_by(key:)`/`.sum()`/`.product()`/`.join(sep:)`/`.iter()`/`.contains(value:)`/`.len()`/`.length()`/`.is_empty()` (sort/min/max require `T: Comparable`; contains requires `T: Eq`) **String methods**: `.split(sep:)`, `.trim()`/`.trim_start()`, `.substring(start:, end:)`/`.slice(start:, end:)`, `.to_lowercase()`, `.starts_with(prefix:)`, `.ends_with(suffix:)`, `.contains(substr:)`, `.last_index_of(substr:)`, `.replace(old:, new:)`, `.repeat(count:)`, `.pad_start(width:, fill:)`/`.pad_end(width:, fill:)`, `.concat(other:)`, `.chars()`/`.iter()`/`.lines()`, `.parse_int()`/`.parse_float()`→`Option`, `.escape()`, `.len()`/`.length()`, `.is_empty()`, `.as_bytes()`→`[byte]` (zero-copy), `.to_bytes()`→`[byte]` (copy), `.byte_len()`→`int` **Char methods**: `.is_alpha()`, `.is_digit()`, `.is_whitespace()`, `.is_uppercase()`, `.is_lowercase()`, `.is_ascii()`, `.to_lowercase()`, `.to_uppercase()`, `.to_byte()`→`byte`, `.to_int()`→`int`, `.to_str()`→`str` **Byte methods**: `.is_ascii()`, `.is_ascii_alpha()`, `.is_ascii_digit()`, `.is_ascii_whitespace()`, `.to_char()`→`char`, `.to_int()`→`int`, `.to_str()`→`str`; arithmetic: `+`/`-`/`*`/`/`/`%` via `add`/`sub`/`mul`/`div`/`rem`; bitwise: `&`/`|`/`^`/`~`/`<<`/`>>` via `bit_and`/`bit_or`/`bit_xor`/`bit_not`/`shl`/`shr` **Compile-Time Reflection**: `fields_of(T)`→`[$FieldMeta]`, `variants_of(T)`→`[$VariantMeta]`, `name_of(T)`→`str` — zero-cost intrinsics, no opt-in; `$FieldMeta { name: str, index: int }`, `$VariantMeta { name: str, index: int, fields: [$FieldMeta] }` — compiler-internal types; `$for field in fields_of(T) yield/do body` — compile-time expansion; `$if const_cond then expr else expr` — dead-branch elimination; `value.[field]` — splice access; `is_struct(T)`, `is_enum(T)`, `is_primitive(T)`, `is_collection(T)`, `is_option(T)`, `is_result(T)`, `is_tuple(T)` — type classification (accept type param or expression) --- # Grammar (EBNF) — syntax single source of truth ```ebnf // ============================================================================ // Ori Language Grammar // Version: 2026 // // This is the unified formal grammar for the Ori programming language. // All productions are authoritative and take precedence over prose descriptions. // // Notation: // production = expression . Production definition // "keyword" Literal token // | Alternation // [ ] Optional (0 or 1) // { } Repetition (0 or more) // ( ) Grouping // /* comment */ Informative note // // Cross-references to detailed explanations are provided in comments. // ============================================================================ // ============================================================================ // LEXICAL GRAMMAR // See: 06-source-code.md, 07-lexical-elements.md // ============================================================================ // --- Characters --- // See: 06-source-code.md § 6.1 Characters, § 6.1.2 Letters and digits unicode_char = /* any Unicode code point except NUL (U+0000) */ . letter = 'A' … 'Z' | 'a' … 'z' . digit = '0' … '9' . hex_digit = digit | 'A' … 'F' | 'a' … 'f' . bin_digit = '0' | '1' . newline = /* U+000A */ . whitespace = ' ' | '\t' | '\r' | newline . // --- Tokens --- token = identifier | keyword | literal | operator | delimiter . // --- Comments --- // See: 07-lexical-elements.md § 7.1 Comments comment = "//" { unicode_char - newline } newline . doc_comment = "//" [ " " ] [ doc_marker ] { unicode_char - newline } newline . doc_marker = "*" | "!" | ">" . member_doc = "//" " " "*" " " identifier ":" [ " " { unicode_char - newline } ] . warning_doc = "//" " " "!" " " { unicode_char - newline } . example_doc = "//" " " ">" " " { unicode_char - newline } . // --- Identifiers --- // See: 07-lexical-elements.md § 7.2 Identifiers identifier = ( letter | "_" ) { letter | digit | "_" } . // --- Keywords --- // See: 07-lexical-elements.md § 7.3 Keywords // // Reserved (36): as, break, continue, def, div, do, else, extend, extension, extern, // false, for, if, impl, in, let, loop, match, Never, pub, self, Self, suspend, // tests, then, trait, true, type, unsafe, use, uses, void, where, while, with, yield // Reserved (future, 5): asm, inline, static, union, view // Context-sensitive (patterns, 9): cache, catch, handler, nursery, parallel, recurse, spawn, // timeout, try // Context-sensitive (blocks, 1): block (before ":" only — labeled block early exit) // Context-sensitive (pattern args, 10): body, buffer, default, expr, map, on_error, over, // pre, post, state // Context-sensitive (imports, 2): from (extern blocks), without (import items, before "def") // Context-sensitive (other, 3): args (@main params), by (after range), max (fixed-capacity lists) // Context-sensitive (embed, 2): embed (file embedding), has_embed (file existence check) // Context-sensitive (type names, 5): bool, byte, float, int, str // Built-in constructors (4): channel, channel_all, channel_in, channel_out // --- Operators --- // See: 07-lexical-elements.md § 7.4 Operators arith_op = "+" | "-" | "*" | "/" | "%" | "div" | "**" . comp_op = "==" | "!=" | "<" | ">" | "<=" | ">=" . logic_op = "&&" | "||" | "!" . bit_op = "&" | "|" | "^" | "~" | "<<" | ">>" . unary_op = "!" | "-" | "~" . other_op = ".." | "..=" | "??" | "?" | "->" | "=>" | "|>" . // --- Delimiters --- delimiter = "(" | ")" | "[" | "]" | "{" | "}" | "," | ":" | "." | "@" | "$" | ";" . // --- Literals --- // See: 07-lexical-elements.md § 7.7 Literals literal = int_literal | float_literal | string_literal | template_literal | char_literal | byte_literal | bool_literal | duration_literal | size_literal . // Integer literals // See: 07-lexical-elements.md § 7.7.1 Integer literals int_literal = decimal_lit | hex_lit | bin_lit . decimal_lit = "0" | non_zero_digit { [ "_" ] digit } . non_zero_digit = "1" … "9" . hex_lit = "0" ( "x" | "X" ) hex_digit { [ "_" ] hex_digit } . bin_lit = "0" ( "b" | "B" ) bin_digit { [ "_" ] bin_digit } . // Float literals // See: 07-lexical-elements.md § 7.7.2 Float literals float_literal = decimal_digits "." decimal_digits [ exponent ] | decimal_digits exponent . decimal_digits = digit { [ "_" ] digit } . exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits . // String literals // See: 07-lexical-elements.md § 7.7.3 String literals string_literal = '"' { string_char | escape_seq } '"' . string_char = unicode_char - ( '"' | '\' | newline ) . escape_seq = '\' ( '"' | '\' | 'n' | 't' | 'r' | '0' ) | unicode_escape | hex_escape . unicode_escape = '\' 'u' '{' hex_digit { hex_digit } '}' . /* 1-6 hex digits */ hex_escape = '\' 'x' hex_digit hex_digit . /* \x00-\xFF */ // Template string literals (with interpolation) // See: 07-lexical-elements.md § 7.7.4 Template strings template_literal = '`' { template_char | template_escape | unicode_escape | template_brace | interpolation } '`' . template_char = unicode_char - ( '`' | '\' | '{' | '}' ) . template_escape = '\' ( '`' | '\' | 'n' | 't' | 'r' | '0' ) . template_brace = "{{" | "}}" . interpolation = '{' expression [ ':' format_spec ] '}' . // Format specifiers for template strings // See: 07-properties-of-types.md § Format Spec Syntax // Syntax: [[fill]align][sign][#][0][width][.precision][type] format_spec = [ [ fill ] align ] [ sign ] [ alt_form ] [ zero_pad ] [ width ] [ '.' precision ] [ format_type ] . fill = unicode_char - ( align | sign ) . align = '<' | '>' | '^' . sign = '+' | '-' | ' ' . alt_form = '#' . zero_pad = '0' . width = decimal_lit . precision = decimal_lit . format_type = 'b' | 'o' | 'x' | 'X' | 'e' | 'E' | 'f' | '%' . // Character literals // See: 07-lexical-elements.md § 7.7.5 Character literals char_literal = "'" ( char_char | char_escape ) "'" . char_char = unicode_char - ( "'" | '\' | newline ) . char_escape = '\' ( "'" | '\' | 'n' | 't' | 'r' | '0' ) | unicode_escape | char_hex_escape . char_hex_escape = '\' 'x' hex_digit hex_digit . /* \x00-\x7F only; \x80-\xFF is an error */ // Byte literals // See: 07-lexical-elements.md § 7.7.6 Byte literals byte_literal = "b'" ( byte_char | byte_escape ) "'" . byte_char = ascii_char - ( "'" | '\' ) . /* U+0020-U+007E, excluding ' and \ */ byte_escape = '\' ( "'" | '\' | 'n' | 't' | 'r' | '0' ) | hex_escape . /* \x00-\xFF; no \u{} or \" */ ascii_char = '\x20' … '\x7E' . /* printable ASCII */ // Boolean literals bool_literal = "true" | "false" . // Duration literals // See: 07-lexical-elements.md § 7.7.8 Duration literals // Decimal syntax (e.g., 0.5s) is compile-time sugar computed via integer arithmetic duration_literal = duration_number duration_unit . duration_number = decimal_lit | decimal_lit "." digit { digit } . duration_unit = "ns" | "us" | "ms" | "s" | "m" | "h" . // Size literals // See: 07-lexical-elements.md § 7.7.9 Size literals // Decimal syntax (e.g., 1.5kb) is compile-time sugar computed via integer arithmetic size_literal = size_number size_unit . size_number = decimal_lit | decimal_lit "." digit { digit } . size_unit = "b" | "kb" | "mb" | "gb" | "tb" . // ============================================================================ // SOURCE STRUCTURE // See: 06-source-code.md, 18-modules.md // ============================================================================ source_file = [ file_attribute ] { import | reexport | extension_import } { declaration } . // File-level attribute (conditional compilation) // See: 24-conditional-compilation.md § File-Level Conditions file_attribute = "#!" identifier "(" [ attribute_arg { "," attribute_arg } ] ")" . // --- Imports --- // See: 12-modules.md § Imports import = "use" import_path [ import_list | "as" identifier ] ";" . import_path = string_literal | identifier { "." identifier } . import_list = "{" import_item { "," import_item } "}" . import_item = [ "::" ] identifier [ "without" "def" ] [ "as" identifier ] | "$" identifier . // --- Re-exports --- reexport = "pub" "use" import_path import_list ";" . // --- Extensions --- // See: 12-modules.md § Extensions extension_def = "extend" [ generics ] type [ where_clause ] "{" { method } "}" . extension_import = [ "pub" ] "extension" import_path "{" extension_item { "," extension_item } "}" . extension_item = identifier "." identifier . // --- FFI (Foreign Function Interface) --- // See: spec/24-ffi.md extern_block = [ "pub" ] "extern" string_literal [ "from" string_literal ] { block_attribute } "{" { extern_item } "}" . extern_item = "@" identifier extern_params "->" [ ownership ] type [ "as" string_literal ] { item_attribute } . extern_params = "(" [ extern_param { "," extern_param } ] [ c_variadic ] ")" . extern_param = [ param_modifier ] identifier ":" [ ownership ] type . c_variadic = "," "..." . /* C-style variadic - only valid in extern "c" blocks */ // Deep FFI annotations (see: proposals/approved/deep-ffi-proposal.md) param_modifier = "out" | "mut" . ownership = "owned" | "borrowed" . block_attribute = "#" identifier "(" { attribute_arg } ")" . /* #error(...), #free(...) */ item_attribute = "#" identifier "(" { attribute_arg } ")" . /* per-function overrides */ // Parametric FFI capability ffi_capability = "FFI" [ "(" string_literal ")" ] . // ============================================================================ // DECLARATIONS // See: 08-declarations.md // ============================================================================ declaration = { attribute } [ "pub" ] ( function | type_def | trait_def | impl_block | extension_def | test | constant_decl | extern_block | capset_decl ) . // --- Attributes --- // See: 08-declarations.md § Attributes, 24-conditional-compilation.md // Item-level: #derive(...), #skip(...), #target(...), #cfg(...) attribute = "#" identifier [ "(" [ attribute_arg { "," attribute_arg } ] ")" ] . attribute_arg = expression | identifier ":" expression | array_literal . array_literal = "[" [ string_literal { "," string_literal } ] "]" . // --- Functions --- // See: 08-declarations.md § Functions function = "@" identifier [ generics ] clause_params "->" type [ uses_clause ] [ where_clause ] [ guard_clause ] { contract } "=" expression [ ";" ] . /* Semicolon rule: ";" is required when the body expression does not end with "}". When the body is a block_expr (ending with "}"), ";" is omitted — same as Rust's fn. */ clause_params = "(" [ clause_param { "," clause_param } ] ")" . clause_param = match_pattern [ ":" type ] [ "=" expression ] . /* pattern with optional type and default */ guard_clause = "if" expression . // --- Generics --- // See: 06-types.md § Const Generic Parameters generics = "<" generic_param { "," generic_param } ">" . generic_param = type_param | const_param . type_param = identifier [ ":" bounds ] [ "=" type ] . const_param = "$" identifier ":" const_type [ "=" const_expr ] . const_type = "int" | "bool" . bounds = type_path { "+" type_path } . where_clause = "where" constraint { "," constraint } . constraint = type_constraint | const_constraint . type_constraint = identifier ":" bounds . const_constraint = const_bound_expr . // Const bound expressions (for where clauses) // See: 06-types.md § Const Bounds const_bound_expr = const_or_expr . const_or_expr = const_and_expr { "||" const_and_expr } . const_and_expr = const_not_expr { "&&" const_not_expr } . const_not_expr = "!" const_not_expr | const_cmp_expr . const_cmp_expr = const_expr comparison_op const_expr | "(" const_bound_expr ")" . comparison_op = ">" | "<" | ">=" | "<=" | "==" | "!=" . // --- Capabilities --- // See: 14-capabilities.md uses_clause = "uses" identifier { "," identifier } . capset_decl = "capset" identifier "=" identifier { "," identifier } ";" . // --- Type Definitions --- // See: 08-declarations.md § Types, 06-types.md § User-Defined Types type_def = "type" identifier [ generics ] [ where_clause ] "=" type_body [ ";" ] . /* Semicolon rule: ";" required for sum types and newtypes (no "}"); omitted for struct types (end with "}") */ type_body = struct_body | sum_body | type . struct_body = "{" [ field { "," field } ] "}" . sum_body = variant { "|" variant } . variant = identifier [ "(" [ field { "," field } ] ")" ] . field = identifier ":" type . // --- Traits --- // See: 08-declarations.md § Traits trait_def = "trait" identifier [ generics ] [ ":" bounds ] "{" { trait_item } "}" . trait_item = method_sig | default_method | assoc_type . method_sig = "@" identifier params "->" type ";" . default_method = "@" identifier params "->" type "=" expression [ ";" ] . /* Semicolon rule: same as function — ";" required unless body ends with "}" */ assoc_type = "type" identifier [ ":" bounds ] [ "=" type ] . params = "(" [ param { "," param } ] ")" . param = identifier ":" type | variadic_param . variadic_param = identifier ":" "..." type . // --- Implementations --- // See: 08-declarations.md § Implementations impl_block = inherent_impl | trait_impl | def_impl . inherent_impl = "impl" [ generics ] type_path [ where_clause ] "{" { method } "}" . trait_impl = "impl" [ generics ] type ":" type_path [ where_clause ] "{" { method } "}" . def_impl = "def" "impl" identifier "{" { def_impl_method } "}" . method = "@" identifier params "->" type [ uses_clause ] "=" expression [ ";" ] . def_impl_method = "@" identifier params "->" type "=" expression [ ";" ] . /* no self, no uses */ /* Semicolon rule: same as function — ";" required unless body ends with "}" */ // --- Tests --- // See: 13-testing.md test = "@" identifier "tests" test_targets "()" "->" "void" "=" expression [ ";" ] . /* Semicolon rule: same as function — ";" required unless body ends with "}" */ test_targets = "_" | test_target { "tests" test_target } . test_target = "@" identifier . // --- Constants (Immutable Bindings) --- // See: 04-constants.md constant_decl = "let" "$" identifier [ ":" type ] "=" expression ";" . // ============================================================================ // TYPES // See: 06-types.md // ============================================================================ type = type_path [ type_args ] | trait_object_bounds /* Printable + Hashable */ | list_type | fixed_list_type | map_type | tuple_type | function_type | impl_trait_type . type_path = identifier { "." identifier } . // --- Bounded Trait Objects --- // See: 06-types.md § Bounded Trait Objects // Multiple trait bounds as a type: `@store (item: Printable + Hashable) -> void` trait_object_bounds = type_path "+" type_path { "+" type_path } . // --- Existential Types (impl Trait) --- // See: 06-types.md § Existential Types impl_trait_type = "impl" trait_bounds [ impl_where_clause ] . trait_bounds = type_path { "+" type_path } . impl_where_clause = "where" assoc_constraint { "," assoc_constraint } . assoc_constraint = identifier "==" type . type_args = "<" type_or_const { "," type_or_const } ">" . type_or_const = type | const_expr . list_type = "[" type "]" . fixed_list_type = "[" type "," "max" const_expr "]" . map_type = "{" type ":" type "}" . tuple_type = "(" type { "," type } ")" | "()" . function_type = "(" [ type { "," type } ] ")" "->" type . /* const_expr is defined in § CONSTANT EXPRESSIONS below */ // ============================================================================ // EXPRESSIONS // See: 09-expressions.md // ============================================================================ expression = with_expr | let_expr | if_expr | for_expr | while_expr | loop_expr | labeled_block | lambda | break_expr | continue_expr | unsafe_expr | block_expr | match_expr | try_expr | binary_expr . // --- Primary Expressions --- primary = literal | identifier | "self" | "Self" | "(" expression ")" | "#" /* length in index context */ | list_literal | map_literal | struct_literal | block_expr /* { expr \n expr \n result } */ | match_expr /* match expr { arms } */ | try_expr /* try { block_body } */ | pattern_expr /* parallel, spawn, etc. */ | unsafe_expr | embed_expr | has_embed_expr . // --- Block Expression --- // See: proposals/approved/block-expression-syntax.md // Amended by: proposals/approved/optional-semicolon-after-block-expressions-proposal.md // Semicolons terminate statements inside blocks. The last expression without ";" // is the block's value. A block where every expression has ";" returns void. // Semicolons are optional after expression statements whose last token is "}". block_expr = "{" { statement } [ expression ] "}" . statement = let_expr ";" | assignment ";" | block_ending_expression [ ";" ] | expression ";" . // An expression whose last token is "}" — semicolon is optional when used as a statement. // The rule is purely syntactic: check whether the last token is "}". block_ending_expression = for_do_expr | while_do_expr | loop_expr | if_expr | match_expr | unsafe_expr | labeled_block | block_expr . // --- Labeled Block --- // See: 16-control-flow.md § Labeled Blocks // "block" is a context-sensitive keyword, recognized only before ":". // break:label exits the block with a value; continue:label targeting a block is an error. labeled_block = "block" label block_expr . // --- Unsafe Expression --- // See: spec/24-ffi.md § Unsafe Blocks unsafe_expr = "unsafe" block_expr . // --- Embed Expressions --- // See: 11-built-in-functions.md § embed, § has_embed // See: proposals/approved/embed-expression-proposal.md embed_expr = "embed" "(" expression ")" . has_embed_expr = "has_embed" "(" expression ")" . // List literals with spread support // See: 09-expressions.md § Spread Operator list_literal = "[" [ list_element { "," list_element } ] "]" . list_element = "..." expression | expression . // Map literals with spread support // See: computed-map-keys-proposal.md for key semantics map_literal = "{" [ map_element { "," map_element } ] "}" . map_element = "..." expression | map_entry . map_entry = map_key ":" expression . map_key = "[" expression "]" /* computed key: evaluates expression */ | identifier /* literal string key: "foo" from foo */ | string_literal . /* literal string key */ // Struct literals with spread support struct_literal = type_path "{" [ struct_element { "," struct_element } ] "}" . struct_element = "..." expression | field_init . field_init = identifier [ ":" expression ] . // --- Postfix Expressions --- // See: 09-expressions.md § Postfix Expressions postfix_expr = primary { postfix_op } . postfix_op = "." member_name [ call_args ] /* field/method access */ | "." match_method /* method-style match */ | "[" expression "]" /* index access */ | call_args /* function call */ | "?" /* error propagation */ | "as" type /* infallible type conversion */ | "as?" type . /* fallible type conversion */ member_name = identifier | keyword | int_literal . /* keywords/ints valid after "." (tuple: t.0) */ match_method = "match" "(" match_arm { "," match_arm } ")" . call_args = "(" [ call_arg { "," call_arg } ] ")" . call_arg = named_arg | positional_arg | spread_arg . named_arg = identifier ":" [ expression ] . /* punned when expression omitted: f(x:) = f(x: x) */ positional_arg = expression . spread_arg = "..." expression . /* spread into variadic position only */ // --- Unary Expressions --- // See: 09-expressions.md § Unary Expressions unary_expr = [ "!" | "-" | "~" ] power_expr . // --- Power Expression --- // See: operator-rules.md § Power // ** binds tighter than unary: -x ** 2 = -(x ** 2) power_expr = postfix_expr [ "**" power_expr ] . /* right-associative */ // --- Binary Expressions --- // See: 09-expressions.md § Binary Expressions // Precedence (lowest to highest): |>, ??, ||, &&, |, ^, &, ==, cmp, range, shift, add, mul, unary, power binary_expr = pipe_expr . pipe_expr = coalesce_expr { "|>" pipe_step } . pipe_step = "." member_name [ call_args ] /* method on piped value */ | postfix_expr [ call_args ] /* function call with implicit fill */ | lambda . /* expression-level operation */ coalesce_expr = or_expr [ "??" coalesce_expr ] . /* right-associative */ or_expr = and_expr { "||" and_expr } . and_expr = bit_or_expr { "&&" bit_or_expr } . bit_or_expr = bit_xor_expr { "|" bit_xor_expr } . bit_xor_expr = bit_and_expr { "^" bit_and_expr } . bit_and_expr = eq_expr { "&" eq_expr } . eq_expr = cmp_expr { ( "==" | "!=" ) cmp_expr } . cmp_expr = range_expr { ( "<" | ">" | "<=" | ">=" ) range_expr } . range_expr = shift_expr [ ( ".." | "..=" ) [ shift_expr ] [ "by" shift_expr ] ] . shift_expr = add_expr { ( "<<" | ">>" ) add_expr } . add_expr = mul_expr { ( "+" | "-" ) mul_expr } . mul_expr = unary_expr { ( "*" | "/" | "%" | "div" | "@" ) unary_expr } . // --- With Expression --- // See: 09-expressions.md § With Expression, 14-capabilities.md § Providing Capabilities with_expr = "with" capability_binding { "," capability_binding } "in" expression . capability_binding = identifier "=" ( handler_expr | expression ) . // --- Handler Expression (Stateful Effect Handlers) --- // See: 14-capabilities.md § Stateful Handlers, proposals/approved/stateful-mock-testing-proposal.md // `handler` is context-sensitive: valid only in the expression position of a capability_binding. handler_expr = "handler" "(" "state" ":" expression ")" "{" handler_operations "}" . handler_operations = handler_operation { "," handler_operation } . handler_operation = identifier ":" expression . // --- Let Binding --- // See: 09-expressions.md § Let Binding, 05-variables.md let_expr = "let" binding_pattern [ ":" type ] "=" expression . assignment = assignment_target "=" expression | assignment_target compound_op expression . assignment_target = identifier { "[" expression "]" | "." identifier } . compound_op = "+=" | "-=" | "*=" | "/=" | "%=" | "**=" | "@=" | "&=" | "|=" | "^=" | "<<=" | ">>=" | "&&=" | "||=" . binding = let_expr | assignment . /* convenience grouping; not currently referenced */ // --- Conditional --- // See: 09-expressions.md § Conditional if_expr = "if" expression "then" expression { "else" "if" expression "then" expression } [ "else" expression ] . // --- For Expression --- // See: 09-expressions.md § For Expression, 19-control-flow.md § Labeled Loops for_expr = "for" [ label ] binding_pattern "in" expression [ "if" expression ] { for_clause } ( "do" | "yield" ) expression . for_clause = "for" binding_pattern "in" expression [ "if" expression ] . // --- Loop Expression --- // See: 09-expressions.md § Loop Expression, 19-control-flow.md § Labeled Loops loop_expr = "loop" [ label ] block_expr . // --- While Expression --- // See: 16-control-flow.md § While Loop // Desugars to: loop { if !condition then break; body } while_expr = "while" [ label ] expression "do" expression . // --- Labels --- // See: 19-control-flow.md § Labeled Loops label = ":" identifier . /* no space around colon */ // --- Lambda --- // See: 09-expressions.md § Lambda lambda = simple_lambda | typed_lambda . simple_lambda = lambda_params "->" expression . typed_lambda = "(" [ typed_param { "," typed_param } ] ")" "->" type "=" expression . lambda_params = identifier | "(" [ identifier { "," identifier } ] ")" . typed_param = identifier ":" type . // --- Control Flow --- // See: 19-control-flow.md break_expr = "break" [ label ] [ expression ] . continue_expr = "continue" [ label ] [ expression ] . // ============================================================================ // PATTERNS (COMPILER CONSTRUCTS) // See: 10-patterns.md // ============================================================================ // --- Pattern Categories --- pattern_expr = function_exp | function_val | channel_expr | for_pattern | catch_expr | nursery_expr . function_exp = pattern_name "(" pattern_arg { "," pattern_arg } ")" . pattern_name = "recurse" | "parallel" | "spawn" | "timeout" | "cache" | "with" . /* nursery has explicit nursery_expr */ pattern_arg = identifier ":" expression . // Type conversion patterns (call position only) function_val = ( "int" | "float" | "str" | "byte" ) "(" expression ")" . // --- Block-Based Expressions --- // See: proposals/approved/block-expression-syntax.md // match: Pattern matching with scrutinee before block match_expr = "match" expression "{" match_arms "}" . match_arms = [ match_arm { "," match_arm } [ "," ] ] . match_arm = match_pattern [ "if" expression ] "->" expression . // try: Error-propagating block (returns early on Err) try_expr = "try" block_expr . // --- Function-Level Contracts --- // See: proposals/approved/block-expression-syntax.md § Function-Level Contracts // Contracts sit between the return type and the "=" in function declarations. contract = pre_contract | post_contract . pre_contract = "pre" "(" check_expr ")" . post_contract = "post" "(" postcheck_expr ")" . check_expr = expression [ "|" string_literal ] . postcheck_expr = lambda_params "->" check_expr . // for pattern: First-match iteration // See: 10-patterns.md § for Pattern for_pattern = "for" "(" for_pattern_args ")" . for_pattern_args = "over" ":" expression "," "match" ":" match_pattern "->" expression "," "default" ":" expression | "over" ":" expression "," "map" ":" expression "," "match" ":" match_pattern "->" expression "," "default" ":" expression . // catch: Panic recovery // See: 20-errors-and-panics.md § Catching Panics catch_expr = "catch" "(" "expr" ":" expression ")" . // nursery: Structured concurrency // See: 10-patterns.md § Concurrency nursery_expr = "nursery" "(" nursery_args ")" . nursery_args = "body" ":" lambda "," "on_error" ":" expression [ "," "timeout" ":" expression ] . // --- Channel Constructors --- // See: 06-types.md § Channel Types channel_expr = channel_constructor "(" "buffer" ":" expression ")" . channel_constructor = "channel" [ type_args ] | "channel_in" [ type_args ] | "channel_out" [ type_args ] | "channel_all" [ type_args ] . // --- Match Patterns --- // See: 10-patterns.md § Match Patterns match_pattern = literal_pattern | identifier_pattern | wildcard_pattern | variant_pattern | struct_pattern | tuple_pattern | list_pattern | range_pattern | or_pattern | at_pattern . literal_pattern = [ "-" ] int_literal | string_literal | char_literal | byte_literal | bool_literal . identifier_pattern = identifier . wildcard_pattern = "_" . variant_pattern = type_path [ "(" [ variant_field { "," variant_field } ] ")" ] . variant_field = identifier ":" [ match_pattern ] /* named; punned if pattern omitted */ | match_pattern . /* positional */ struct_pattern = [ type_path ] "{" [ field_pattern { "," field_pattern } ] [ ".." ] "}" . field_pattern = identifier [ ":" match_pattern ] . tuple_pattern = "(" [ match_pattern { "," match_pattern } ] ")" . list_pattern = "[" [ list_pattern_elems ] "]" . list_pattern_elems = match_pattern { "," match_pattern } [ "," ".." [ identifier ] ] | ".." [ identifier ] . range_pattern = const_pattern ( ".." | "..=" ) const_pattern . const_pattern = literal_pattern | "$" identifier . or_pattern = match_pattern "|" match_pattern . at_pattern = identifier "@" match_pattern . // --- Binding Patterns --- // See: 05-variables.md § Destructuring // The "$" prefix marks immutable bindings; without "$", bindings are mutable. binding_pattern = [ "$" ] identifier | "_" | "{" [ field_binding { "," field_binding } ] "}" | "(" [ binding_pattern { "," binding_pattern } ] ")" | "[" [ binding_pattern { "," binding_pattern } [ ".." [ "$" ] identifier ] ] "]" . field_binding = [ "$" ] identifier [ ":" binding_pattern ] . // ============================================================================ // CONSTANT EXPRESSIONS // See: 04-constants.md, 21-constant-expressions.md // ============================================================================ const_expr = literal | "$" identifier | const_expr ( arith_op | comp_op | "&&" | "||" | "&" | "|" | "^" | "<<" | ">>" ) const_expr | unary_op const_expr | "(" const_expr ")" . // ============================================================================ // PROGRAM ENTRY // See: 18-program-execution.md // ============================================================================ main_function = "@main" main_params "->" main_return "=" expression [ ";" ] . main_params = "()" | "(" "args" ":" "[" "str" "]" ")" . main_return = "void" | "int" | "Never" . // ============================================================================ // END OF GRAMMAR // ============================================================================ ``` --- --- title: "Operator Rules" description: "Ori Language Specification — Operator typing and evaluation rules" order: 101 section: "Annexes" --- # Operator Rules Formal typing and evaluation rules for Ori operators. ## Legend ``` NOTATION ──────── T, U, E type variables v, v1, v2 values e, e1, e2 expressions env type environment |- "entails" (type judgment) -> type-level transformation => evaluation (reduces to) ───── inference rule separator (premises above, conclusion below) [cond] side condition x type product (left x right) TYPE RULES ────────── premise1 premise2 ──────────────────── RULE-NAME conclusion READ AS: "if premise1 and premise2 hold, then conclusion holds" EVALUATION RULES ──────────────── pattern => result [condition] READ AS: "pattern evaluates to result when condition holds" ASSOCIATIVITY ───────────── assoc=left left-to-right grouping: a op b op c = (a op b) op c assoc=right right-to-left grouping: a op b op c = a op (b op c) MAINTENANCE ─────────── - Add new operators: copy existing block, modify rules - Modify behavior: update relevant => rules - Type changes: update -> rules and inference rules - Keep in sync with: grammar.ebnf, ori_types/src/infer/expr/operators.rs, ori_eval/src/interpreter/mod.rs ``` --- ## Pipe `|>` ``` assoc=left prec=16 (lowest binary) TYPE RULES ────────── e1 : T f : (P: T, ...) -> U [P is single unspecified param of f] ───────────────────────────────────────────────────────────────────── PIPE-FILL e1 |> f(...) : U e1 : T method : (self: T, ...) -> U ────────────────────────────────────── PIPE-METHOD e1 |> .method(...) : U e1 : T g : (T) -> U ──────────────────────── PIPE-LAMBDA e1 |> g : U UNSPECIFIED PARAMETER ───────────────────── A parameter is "unspecified" when: (a) not provided in the call arguments, AND (b) has no default value Parameters with defaults are treated as filled for pipe resolution. COMPILE ERRORS ────────────── Zero unspecified params => "all parameters already specified; nothing for pipe to fill" 2+ unspecified params => "ambiguous pipe target; specify all parameters except one" DESUGARING ────────── e1 |> f(a: v) => { let $__pipe = e1; f(: __pipe, a: v) } e1 |> .method(a: v) => { let $__pipe = e1; __pipe.method(a: v) } e1 |> (x -> expr) => { let $__pipe = e1; (x -> expr)(__pipe) } e1 |> f(a: v)? => { let $__pipe = e1; f(: __pipe, a: v)? } LEFT-TO-RIGHT: a |> f |> g |> h = h(g(f(a))) ``` --- ## Coalesce `??` ``` assoc=right prec=15 TYPE RULES ────────── e1 : Option e2 : T ──────────────────────── COALESCE-UNWRAP e1 ?? e2 : T e1 : Option e2 : Option ──────────────────────────────── COALESCE-CHAIN e1 ?? e2 : Option e1 : Result e2 : T ────────────────────────── COALESCE-RESULT-UNWRAP e1 ?? e2 : T e1 : Result e2 : Result ──────────────────────────────────── COALESCE-RESULT-CHAIN e1 ?? e2 : Result e1 : Never e2 : T [Never unifies with Option] ──────────────────────────────────────────────────── COALESCE-NEVER-LEFT e1 ?? e2 : T e1 : Option e2 : Never ──────────────────────────── COALESCE-NEVER-RIGHT e1 ?? e2 : T EVALUATION ────────── Some(v) ?? e2 => v [type(e1) != type(e1 ?? e2)] Some(v) ?? e2 => Some(v) [type(e1) = type(e1 ?? e2)] None ?? e2 => eval(e2) Ok(v) ?? e2 => v [type(e1) != type(e1 ?? e2)] Ok(v) ?? e2 => Ok(v) [type(e1) = type(e1 ?? e2)] Err(_) ?? e2 => eval(e2) SHORT-CIRCUIT: e2 not evaluated when e1 is Some/Ok ``` --- ## Arithmetic `+` `-` `*` `/` `%` `div` ``` assoc=left prec=4 (* / % div @), prec=5 (+ -) TYPE RULES ────────── e1 : int e2 : int ──────────────────── ARITH-INT e1 op e2 : int e1 : float e2 : float ──────────────────────── ARITH-FLOAT e1 op e2 : float e1 : str e2 : str ──────────────────── CONCAT e1 + e2 : str e1 : Duration e2 : Duration ────────────────────────────── DURATION-ADD-SUB e1 +|- e2 : Duration e1 : Duration e2 : int ───────────────────────── DURATION-MUL-DIV e1 *|/ e2 : Duration e1 : int e2 : Duration ───────────────────────── DURATION-MUL-REV e1 * e2 : Duration e1 : Duration e2 : Duration ────────────────────────────── DURATION-MOD e1 % e2 : Duration e1 : Size e2 : Size ────────────────────── SIZE-ADD-SUB e1 +|- e2 : Size e1 : Size e2 : int ───────────────────── SIZE-MUL-DIV e1 *|/ e2 : Size e1 : int e2 : Size ───────────────────── SIZE-MUL-REV e1 * e2 : Size e1 : Size e2 : Size ────────────────────── SIZE-MOD e1 % e2 : Size EVALUATION ────────── n1 + n2 => sum [overflow -> panic] n1 - n2 => diff [overflow -> panic] n1 * n2 => product [overflow -> panic] n1 / n2 => quotient [n2 = 0 -> panic, truncates toward zero] n1 % n2 => remainder [n2 = 0 -> panic] n1 div n2 => floor_quot [n2 = 0 -> panic, floor toward -inf] s1 + s2 => concat ``` --- ## Power `**` ``` assoc=right prec=2 (tighter than unary, looser than postfix) TYPE RULES ────────── e1 : int e2 : int ──────────────────── POW-INT e1 ** e2 : int e1 : float e2 : float ────────────────────────── POW-FLOAT e1 ** e2 : float e1 : float e2 : int ───────────────────────── POW-FLOAT-INT e1 ** e2 : float e1 : int e2 : float ───────────────────────── POW-INT-FLOAT e1 ** e2 : float EVALUATION ────────── n1 ** n2 => int_power [n1 : int, n2 : int, n2 >= 0] n1 ** n2 => panic [n1 : int, n2 : int, n2 < 0, "negative exponent on integer"] n1 ** 0 => 1 [for all n1, including 0 ** 0] f1 ** f2 => libm_pow [delegates to libm pow()] OVERFLOW ──────── int ** int follows standard overflow behavior (panic in debug) TRAIT DISPATCH ────────────── ** -> Pow -> power(self, rhs:) ``` --- ## Comparison `==` `!=` `<` `<=` `>` `>=` ``` assoc=left prec=8 (< <= > >=), prec=9 (== !=) TYPE RULES ────────── e1 : T e2 : T T : Eq ────────────────────────── EQ e1 ==|!= e2 : bool e1 : T e2 : T T : Comparable ────────────────────────────────── ORD e1 <|<=|>|>= e2 : bool EVALUATION ────────── v == v => true v1 == v2 => false [v1 != v2] v != v => false v1 != v2 => true [v1 != v2] v1 < v2 => compare(v1, v2) = Less v1 <= v2 => compare(v1, v2) != Greater v1 > v2 => compare(v1, v2) = Greater v1 >= v2 => compare(v1, v2) != Less ``` --- ## Logical `&&` `||` ``` assoc=left prec=13 (&&), prec=14 (||) TYPE RULES ────────── e1 : bool e2 : bool ────────────────────── AND e1 && e2 : bool e1 : bool e2 : bool ────────────────────── OR e1 || e2 : bool EVALUATION ────────── false && e2 => false [e2 not evaluated] true && e2 => eval(e2) true || e2 => true [e2 not evaluated] false || e2 => eval(e2) ``` --- ## Bitwise `&` `|` `^` `<<` `>>` ``` assoc=left prec=10 (&), prec=11 (^), prec=12 (|), prec=6 (<< >>) TYPE RULES ────────── e1 : int e2 : int ──────────────────────── BITWISE-INT e1 &|^|<<|>> e2 : int e1 : byte e2 : byte ────────────────────────── BITWISE-BYTE e1 &|^ e2 : byte e1 : byte e2 : int ───────────────────────── SHIFT-BYTE e1 <<|>> e2 : byte EVALUATION ────────── n1 & n2 => bitwise_and n1 | n2 => bitwise_or n1 ^ n2 => bitwise_xor n1 << n2 => shift_left [n2 < 0 -> panic, n2 >= width -> panic] n1 >> n2 => shift_right [n2 < 0 -> panic, n2 >= width -> panic] CONSTRAINTS ─────────── int width = 64 bits (valid shift: 0..63) byte width = 8 bits (valid shift: 0..7) ``` --- ## Range `..` `..=` ``` assoc=left prec=7 TYPE RULES ────────── e1 : int e2 : int ──────────────────────── RANGE e1 ..|..= e2 : Range e1 : int ──────────────────── RANGE-OPEN e1 .. : Range e1 : int e2 : int e3 : int ──────────────────────────────── RANGE-STEP e1 ..|..= e2 by e3 : Range ``` --- ## Unary `-` `!` `~` ``` prec=3 (between power and multiplicative) TYPE RULES ────────── e : int ─────────── NEG-INT -e : int e : float ───────────── NEG-FLOAT -e : float e : Duration ──────────────── NEG-DURATION -e : Duration e : bool ──────────── NOT !e : bool e : int ─────────── BITNOT-INT ~e : int e : byte ──────────── BITNOT-BYTE ~e : byte CONSTRAINTS ─────────── -Size -> compile error (Size cannot be negative) ``` --- ## Type Conversion `as` `as?` ``` prec=1 (postfix) TYPE RULES ────────── e : T T converts to U (infallible) ────────────────────────────────────── AS-INFALLIBLE e as U : U e : T T converts to U (fallible) ──────────────────────────────────── AS-FALLIBLE e as? U : Option INFALLIBLE CONVERSIONS ────────────────────── int -> float byte -> int char -> int FALLIBLE CONVERSIONS ──────────────────── str -> int (parse) str -> float (parse) int -> byte (range check) float -> int (truncate, range check) ``` --- ## Try `?` ``` prec=1 (postfix) TYPE RULES ────────── e : Option enclosing returns Option ──────────────────────────────────────────── TRY-OPTION e? : T e : Result enclosing returns Result ──────────────────────────────────────────────── TRY-RESULT e? : T EVALUATION ────────── Some(v)? => v None? => return None Ok(v)? => v Err(e)? => return Err(e) ``` --- ## Never ``` UNIFICATION ─────────── unify(Never, T) = Ok for all T unify(T, Never) = Ok for all T COERCION ──────── e : Never ───────── NEVER-COERCE e : T for all T SOURCES ─────── panic(msg:) : Never todo() : Never todo(reason:) : Never unreachable() : Never unreachable(reason:) : Never break : Never [in loop] continue : Never [in loop] loop { e } : Never [no break in e] e? : Never [early return path when e is None/Err] ``` --- ## Precedence Table ``` PREC OPERATORS ASSOC DESCRIPTION ──── ───────── ───── ─────────── 1 . [] () ? as as? left postfix 2 ** right power 3 ! - ~ right unary 4 * / % div @ left multiplicative 5 + - left additive 6 << >> left shift 7 .. ..= [by] left range (by is step modifier) 8 < > <= >= left comparison 9 == != left equality 10 & left bitwise and 11 ^ left bitwise xor 12 | left bitwise or 13 && left logical and 14 || left logical or 15 ?? RIGHT coalesce ``` --- ## Trait Dispatch ``` OPERATOR -> TRAIT -> METHOD ─────────────────────────── + -> Add -> add(self, other:) - -> Sub -> subtract(self, other:) * -> Mul -> multiply(self, other:) / -> Div -> divide(self, other:) div -> FloorDiv -> floor_divide(self, other:) % -> Rem -> remainder(self, other:) ** -> Pow -> power(self, rhs:) @ -> MatMul -> matrix_multiply(self, rhs:) - -> Neg -> negate(self) ! -> Not -> not(self) ~ -> BitNot -> bit_not(self) & -> BitAnd -> bit_and(self, other:) | -> BitOr -> bit_or(self, other:) ^ -> BitXor -> bit_xor(self, other:) << -> Shl -> shift_left(self, rhs:) >> -> Shr -> shift_right(self, rhs:) == -> Eq -> equals(self, other:) < -> Comparable -> compare(self, other:) RESOLUTION ORDER ──────────────── 1. Primitive type -> direct evaluation 2. User type -> trait method lookup ``` --- ## Compound Assignment ``` DESUGARING ────────── x op= y => x = x op y [parser-level rewrite] SUPPORTED OPERATORS (TRAIT-BASED) ───────────────────────────────── += desugars via Add -= desugars via Sub *= desugars via Mul /= desugars via Div %= desugars via Rem **= desugars via Pow @= desugars via MatMul &= desugars via BitAnd |= desugars via BitOr ^= desugars via BitXor <<= desugars via Shl >>= desugars via Shr SUPPORTED OPERATORS (LOGICAL) ───────────────────────────── &&= desugars to x = x && y [bool-only, short-circuit preserved] ||= desugars to x = x || y [bool-only, short-circuit preserved] CONSTRAINTS ─────────── - Left-hand side shall be a mutable binding (no `$` prefix) - Compound assignment is a statement, not an expression - Target expression is duplicated in AST (pure: no side effects) ``` --- --- title: "Scope" description: "Ori Language Specification — Clause 1: Scope" order: 1 section: "Preliminary" --- # 1 Scope This document specifies: - the representation of Ori programs; - the syntax and constraints of the Ori language; - the semantic rules for interpreting Ori programs; - the standard library types, traits, and functions available to conforming programs; - the restrictions and limits imposed by a conforming implementation. This document does not specify: - the mechanism by which Ori programs are compiled or executed; - the mechanism by which Ori programs receive input or produce output; - the size or complexity of an Ori program that exceeds the capacity of any specific implementation; - implementation-defined extensions. --- --- title: "Normative references" description: "Ori Language Specification — Clause 2: Normative references" order: 2 section: "Preliminary" --- # 2 Normative references The following documents are referred to in the text in such a way that some or all of their content constitutes requirements of this document. - **The Unicode Standard, Version 15.0** — Unicode Consortium. Character encoding and properties for `str` and `char` types. - **IEC 60559:2020** — *Floating-point arithmetic*. Semantics of the `float` type. --- --- title: "Terms and definitions" description: "Ori Language Specification — Clause 3: Terms and definitions" order: 3 section: "Preliminary" --- # 3 Terms and definitions For the purposes of this document, the following terms and definitions apply. ISO/IEC 2382 and IEEE 754 define terms used in this document that are not defined here. ## 3.1 program complete set of modules forming a self-contained unit of compilation and execution (Clause 23) NOTE A conforming program has exactly one entry point. ## 3.2 module unit of source code organization containing declarations, identified by a file path or package-qualified name (Clause 18) ## 3.3 package collection of related modules distributed as a unit (Clause 18) ## 3.4 entry point function designated as the starting point of program execution, declared with the `@main` attribute (Clause 23) ## 3.5 scope region of program text within which a binding is visible (Clause 11) ## 3.6 block delimited sequence of statements and an optional trailing expression that produces a value (Clause 11) NOTE The value of a block is determined by its last expression, if present. A block in which all statements are terminated by `;` has type `void`. ## 3.7 shadowing introduction of a new binding with the same name as an existing binding in an enclosing scope, rendering the outer binding inaccessible within the inner scope (Clause 11) ## 3.8 type classification determining the set of values and applicable operations for an entity (Clause 8) ## 3.9 primitive type type built into the language whose representation is defined by the implementation (Clause 8) NOTE The primitive types are `int`, `float`, `bool`, `str`, `char`, `byte`, and `void`. ## 3.10 bottom type type that has no values, denoted `Never`, which is a subtype of every other type (Clause 8) NOTE Expressions that never produce a value, such as `panic()`, `todo()`, and infinite loops without `break`, have the bottom type. ## 3.11 collection type type representing an ordered or keyed group of elements (Clause 8) NOTE The collection types are list (`[T]`), fixed-capacity list (`[T, max N]`), map (`{K: V}`), and `Set`. ## 3.12 generic type type parameterized by one or more type parameters, enabling polymorphic definitions (Clause 8) ## 3.13 type parameter placeholder for a type within a generic definition, resolved to a concrete type at each use site (Clause 8) ## 3.14 trait object dynamically dispatched value whose concrete type is erased, retaining only the interface of a specified trait (Clause 9) ## 3.15 associated type type declared within a trait that implementing types shall define concretely (Clause 10) ## 3.16 newtype distinct type defined as a wrapper around an existing type, providing type safety with zero runtime cost (Clause 8) NOTE A newtype does not inherit traits or methods from its inner type. ## 3.17 existential type opaque return type declared as `impl Trait`, hiding the concrete type from callers while guaranteeing static dispatch (Clause 8) ## 3.18 value element of a type's value set, produced by evaluating an expression (Clause 14) ## 3.19 binding association between a name and a value within a scope (Clause 13) ## 3.20 immutable binding binding declared with the `$` sigil whose value cannot be reassigned after initialization (Clause 13) ## 3.21 mutable binding binding declared with `let` (without `$`) whose value may be reassigned (Clause 13) ## 3.22 constant module-level immutable binding declared with `let $name` whose value is determined at compile time (Clause 12) ## 3.23 literal syntactic representation of a fixed value of a primitive or collection type (Clause 7) ## 3.24 constant expression expression whose value can be fully determined at compile time without executing the program (Clause 24) ## 3.25 function named callable entity with typed parameters and a return type, defined using the `@` sigil (Clause 10) ## 3.26 clause one of multiple definitions of a function that are distinguished by their parameter patterns and evaluated top-to-bottom (Clause 10) NOTE Clauses enable pattern-based dispatch, similar to function clauses in Erlang or Elixir. ## 3.27 parameter named, typed input to a function, declared in the function signature (Clause 10) ## 3.28 guard boolean condition attached to a function clause or match arm that shall evaluate to `true` for the clause or arm to be selected (Clause 10) ## 3.29 variadic parameter parameter declared with the `...` prefix, accepting zero or more arguments that are received as a list (Clause 10) ## 3.30 method function defined within an `impl` block or trait that takes `self` as its first parameter (Clause 10) ## 3.31 associated function function defined within an `impl` block that does not take `self`, called on the type rather than an instance (Clause 10) ## 3.32 default implementation method body provided in a trait definition, used when an implementing type does not override it (Clause 10) ## 3.33 trait named interface declaring a set of methods and associated types that types may implement (Clause 9) ## 3.34 implementation block of method definitions that provides concrete behavior for a trait on a specific type, or inherent methods on a type (Clause 10) ## 3.35 trait bound constraint requiring a type parameter to implement one or more specified traits (Clause 9) ## 3.36 coherence property ensuring that for any given type and trait, at most one implementation exists in the program (Clause 9) ## 3.37 derived trait trait whose implementation is automatically generated by the compiler based on the structure of the type (Clause 9) NOTE The `#derive` attribute requests derivation. Derivable traits include `Eq`, `Clone`, `Debug`, `Comparable`, and `Hashable`. ## 3.38 object safety property of a trait that determines whether it can be used as a trait object for dynamic dispatch (Clause 9) NOTE A trait is object-safe if none of its methods return `Self`, take `Self` as a non-receiver parameter, or are generic. ## 3.39 pattern syntactic construct used to test the structure of a value and optionally bind components to variables (Clause 15) ## 3.40 match arm component of a `match` expression consisting of a pattern, an optional guard, and a body expression (Clause 15) ## 3.41 exhaustive match `match` expression in which the set of arms covers all possible values of the scrutinee type (Clause 15) ## 3.42 scrutinee expression whose value is tested against the patterns of a `match` expression (Clause 15) ## 3.43 automatic reference counting memory management strategy in which each value maintains a count of references to it, and is deallocated when the count reaches zero (Clause 21) ## 3.44 reference count non-negative integer associated with a heap-allocated value, tracking the number of live references to that value (Clause 21) ## 3.45 closure function value that captures bindings from its enclosing scope by value (Clause 14) ## 3.46 value capture mechanism by which a closure obtains copies of bindings from its enclosing scope at the point of closure creation (Clause 14) NOTE Ori captures all bindings by value. There is no capture by reference. ## 3.47 capability named effect that a function declares it requires, enabling the caller to provide or mock the implementation (Clause 20) ## 3.48 handler value provided in a `with...in` expression that satisfies a required capability for the duration of the enclosed expression (Clause 20) ## 3.49 suspend capability the `Suspend` capability, which indicates that a function may suspend its execution to participate in cooperative concurrency (Clause 20) ## 3.50 test function annotated with the `tests` attribute that verifies the behavior of one or more target functions (Clause 19) ## 3.51 attached test test declared with `tests @target`, establishing a dependency relationship with the target function (Clause 19) ## 3.52 floating test test declared with `tests _`, not attached to any specific function (Clause 19) ## 3.53 contract precondition or postcondition declared on a function using `pre()` or `post()`, checked at run time (Clause 19) ## 3.54 panic unrecoverable error condition that terminates execution of the current task (Clause 17) NOTE Panics are not exceptions and cannot be caught. They produce a diagnostic and abort. ## 3.55 recoverable error error condition represented as a value of type `Result`, propagable via the `?` operator (Clause 17) ## 3.56 error propagation mechanism by which the `?` operator returns early from a function with an `Err` value when applied to a `Result` or `None` when applied to an `Option` (Clause 17) ## 3.57 task unit of concurrent execution within a nursery, representing a suspendable computation (Clause 22) ## 3.58 suspending context execution environment in which `Suspend` capability is available, permitting cooperative task switching (Clause 22) ## 3.59 suspension point point in a task's execution at which it may yield control to the scheduler, occurring at operations that `use Suspend` (Clause 22) --- --- title: "Conformance" description: "Ori Language Specification — Clause 4: Conformance" order: 4 section: "Preliminary" --- # 4 Conformance ## 4.1 Conforming implementation A _conforming implementation_ of Ori is one that accepts and correctly executes any conforming program, subject only to resource limitations. A conforming implementation shall: - accept all conforming programs; - reject all non-conforming programs with appropriate diagnostics; - produce the behavior specified by this document for all conforming programs. ## 4.2 Conforming program A _conforming program_ is one that is accepted by a conforming implementation and whose behavior is fully defined by this document. ## 4.3 Extensions A conforming implementation may have extensions, provided they do not alter the behavior of any conforming program. ## 4.4 Implementation-defined behavior Certain aspects of this specification are described as _implementation-defined_. A conforming implementation shall document its choices for all implementation-defined behavior. ## 4.5 Undefined behavior Ori is designed to minimize undefined behavior. Where this document does not specify behavior, a conforming implementation shall either: - reject the program with a diagnostic; or - produce a panic at run time. Silent undefined behavior is not permitted in a conforming implementation. --- --- title: "Notation" description: "Clause 5: Ori Language Specification — Notation" order: 5 section: "Language" --- # 5 Notation The syntax is specified using Extended Backus-Naur Form (EBNF). > **Grammar:** The complete formal grammar is in [grammar.ebnf](grammar.md). ## 5.1 Productions Productions are expressions terminated by `.`: ``` production_name = expression . ``` ## 5.2 Operators | Notation | Meaning | |----------|---------| | `a b` | Sequence | | `a \| b` | Alternation | | `[ a ]` | Optional (0 or 1) | | `{ a }` | Repetition (0 or more) | | `( a )` | Grouping | | `"x"` | Literal keyword | | `'c'` | Literal character | | `'a' … 'z'` | Character range (inclusive) | ## 5.3 Naming | Style | Usage | |-------|-------| | `lower_case` | Production names | | `PascalCase` | Type names | | `snake_case` | Functions, variables | ## 5.4 Terminology | Term | Meaning | |-------------|---------------------------------------------------| | shall | Requirement (ISO/IEC Directives, Part 2) | | shall not | Prohibition | | should | Recommendation | | may | Permission | | can | Possibility or capability | | error | Compile-time failure | | panic | Run-time failure | ## 5.5 Examples EXAMPLE 1 ```ori @add (a: int, b: int) -> int = a + b; ``` EXAMPLE 2 The following is not valid because the return type is omitted: ```ori @add (a: int, b: int) = a + b; // error: missing return type ``` --- --- title: "Source Code Representation" description: "Clause 6: Ori Language Specification — Source Code Representation" order: 6 section: "Language" --- # 6 Source Code Representation Source code is Unicode text encoded in UTF-8. The text is not canonicalized, so a single accented code point is distinct from the same character constructed from combining an accent and a letter; those are treated as two code points. This document uses the unqualified term _character_ to refer to a Unicode code point in the source text. Each code point is distinct; for instance, uppercase and lowercase letters are different characters. > **Grammar:** See [grammar.ebnf](grammar.md) § LEXICAL GRAMMAR, Characters ## 6.1 Characters The following terms denote specific character categories: ```ebnf newline = /* the Unicode code point U+000A */ . unicode_char = /* any Unicode code point except NUL (U+0000) */ . ``` A _unicode character_ is any Unicode code point except U+0000 (NUL). The NUL byte shall not appear in source text; its presence is an error. _Control characters_ U+0001 through U+001F, except for the following, shall not appear in source text outside of string and character literals: - U+0009 (horizontal tab) - U+000A (newline) - U+000D (carriage return) The presence of any other control character outside a string or character literal is an error. NOTE Control characters may be represented in string and character literals using escape sequences (see [7.7.3.1](07-lexical-elements.md#7731-escape-sequences)). _Comments_ (see [7.1](07-lexical-elements.md#71-comments)) may contain any _unicode character_. ### 6.1.1 Line endings A carriage return U+000D immediately followed by a newline U+000A is normalized to a single newline. A lone carriage return not followed by a newline is also treated as a newline. After normalization, all line endings in source text are represented as U+000A. ### 6.1.2 Letters and digits The following terms define the character categories used by identifier rules in [Clause 7](07-lexical-elements.md): ```ebnf letter = 'A' … 'Z' | 'a' … 'z' . digit = '0' … '9' . ``` Identifiers are restricted to ASCII letters, ASCII digits, and underscore (see [7.2](07-lexical-elements.md#72-identifiers)). The underscore character `_` (U+005F) is considered a letter for the purpose of identifier formation. NOTE Future editions may expand identifier characters to include Unicode letter and digit categories. The current restriction simplifies tooling and ensures all identifiers are representable in ASCII. ## 6.2 Source Files Source files use the `.ori` extension. Test files use the `.test.ori` extension. File names shall: - begin with an ASCII letter (`A`–`Z`, `a`–`z`) or underscore (`_`) - contain only ASCII letters, ASCII digits (`0`–`9`), and underscores - end with `.ori` or `.test.ori` EXAMPLE Valid file names: `main.ori`, `http_client.ori`, `math_test.test.ori`. EXAMPLE The following are not valid file names: `2fast.ori` (starts with digit), `my-module.ori` (contains hyphen), `module.ORI` (wrong extension case). ## 6.3 Encoding Source files shall be valid UTF-8 without byte order mark (BOM, U+FEFF). The presence of a BOM at any position is an error. Invalid UTF-8 byte sequences are an error. NOTE Some editors insert a UTF-8 BOM. Ori rejects it to guarantee that byte offsets are unambiguous from the first byte. ## 6.4 Source Positions A _source position_ identifies a location in source text for use in diagnostics and trace entries. A _line_ is a sequence of characters terminated by a newline (after normalization per 6.1.1) or by the end of the source file. Lines are numbered sequentially starting at 1. A _column_ is the byte offset from the start of a line, numbered starting at 1. The first byte of a line is column 1. NOTE Column numbers count UTF-8 bytes, not Unicode code points or grapheme clusters. A multi-byte character occupies multiple column positions. This matches the convention used by the Language Server Protocol and common editors. Source positions are represented at runtime by the `TraceEntry` type (see [9.9.1](09-types.md#991-traceentry)), which has `line: int` and `column: int` fields following these conventions. EXAMPLE In the line `let $π = 3`, the identifier `π` (U+03C0, encoded as 2 bytes CE B0) occupies columns 6–7. The `=` is at column 9. ## 6.5 Line Continuation Ori uses _implicit line continuation_: a newline does not terminate a logical line when the current token context indicates that the expression is incomplete. A newline is not a statement terminator when the last token before the newline is: - a binary operator: `+`, `-`, `*`, `/`, `%`, `**`, `div`, `@`, `&&`, `||`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `..`, `..=`, `??`, `|>`, `&`, `|`, `^`, `<<`, `>>` - an opening delimiter: `(`, `[`, `{` - a comma (`,`), assignment operator (`=`, `+=`, `-=`, etc.), arrow (`->`), or colon (`:`) NOTE This list is exhaustive. Closing delimiters, identifiers, literals, and keywords at end of line do terminate the logical line unless they match one of the continuation tokens above. ## 6.6 Module Mapping Each source file defines one _module_. The module name derives from the file path relative to the source root: | File Path | Module Name | |-----------|-------------| | `src/main.ori` | `main` | | `src/http/client.ori` | `http.client` | | `src/utils/string_helpers.ori` | `utils.string_helpers` | Directory separators in the file path map to dots (`.`) in the module name. The `.ori` extension is stripped. See [Clause 18](18-modules.md) for the complete module system specification. ## 6.7 Source File Constraints The maximum source file size is implementation-defined. An implementation shall accept source files of at least 2 147 483 647 bytes (2^31 − 1). A source file with no declarations produces no module and has no effect on the program. NOTE There is no requirement for a trailing newline at end of file, though the formatter inserts one. NOTE Line length is not limited by the language. The formatter enforces a 100-character line width as a style guideline; the specification imposes no such constraint. --- --- title: "Lexical Elements" description: "Clause 7: Ori Language Specification — Lexical Elements" order: 7 section: "Language" --- # 7 Lexical Elements > **Grammar:** See [grammar.ebnf](grammar.md) § LEXICAL GRAMMAR A _token_ is the smallest lexical unit of the language. The five token classes are: identifiers, keywords, literals, operators, and delimiters. Whitespace — consisting of space (U+0020), horizontal tab (U+0009), and newline (U+000A, after normalization per [6.1.1](06-source-code.md#611-line-endings)) — separates tokens but is otherwise not significant. Ori is not indentation-sensitive. At least one whitespace character or delimiter shall separate adjacent tokens that would otherwise form a single token. For instance, `intx` is the identifier `intx`, not the keyword `int` followed by the identifier `x`. ## 7.1 Comments Comments begin with `//` and extend to the end of the line. Inline comments (comments following code on the same line) are not permitted. ```ori // This is a valid comment @add (a: int, b: int) -> int = a + b; @sub (a: int, b: int) -> int = a - b; // error: inline comment ``` The content of a comment is arbitrary text: any _unicode_char_ (see [6.1](06-source-code.md#61-characters)) except newline may appear in a comment. ### 7.1.1 Doc comments Doc comments use special markers to classify documentation content: | Marker | Purpose | Example | |--------|---------|---------| | *(none)* | Description | `// This is a description.` | | `*` | Parameter or field | `// * name: Description` | | `!` | Warning or panic | `// ! Panics if x is negative` | | `>` | Example | `// > func(x: 1) -> 2` | The canonical form for member documentation is `// * name: description` with a space after `*` and a colon always required. Any comment immediately preceding a declaration is treated as documentation for that declaration. Non-documentation comments shall be separated from declarations by a blank line: ```ori // TODO: refactor this later // Computes the sum of two integers. @add (a: int, b: int) -> int = a + b; ``` ## 7.2 Identifiers An _identifier_ names a program entity such as a variable, type, function, or module. ```ebnf identifier = ( letter | "_" ) { letter | digit | "_" } . ``` where `letter` and `digit` are as defined in [6.1.2](06-source-code.md#612-letters-and-digits). Identifiers are restricted to ASCII letters, ASCII digits, and underscore. They are case-sensitive: `count`, `Count`, and `COUNT` are three distinct identifiers. An identifier shall not be a reserved keyword (see 7.3.1) or future-reserved keyword (see 7.3.2) unless it appears in member position (after `.`). The maximum identifier length is implementation-defined; an implementation shall accept identifiers of at least 1 000 characters. Identifiers are compared byte-for-byte. No Unicode normalization (NFC, NFD, or other) is applied. NOTE The `$` and `@` prefixes are _sigils_ (see 7.6), not part of the identifier. In the binding `let $timeout = 30s`, the identifier is `timeout`; the `$` marks it as immutable. EXAMPLE Valid identifiers: `x`, `_count`, `Point`, `my_function`, `x2`, `HTTP_STATUS`. EXAMPLE The following are not identifiers: `2fast` (starts with digit), `my-var` (contains hyphen), `let` (reserved keyword). ### 7.2.1 Predeclared identifiers Certain identifiers are _predeclared_ in the universe scope (see [11.1](11-blocks-and-scope.md)). These include type names (`int`, `float`, `str`, `byte`, `bool`, `void`, `Never`), built-in functions (`print`, `panic`, `len`, etc.), and predefined types (`Option`, `Result`, `Error`, `Ordering`). See [Annex C](annex-c-built-in-functions.md) for the complete list. Predeclared identifiers may be shadowed by user declarations in inner scopes. ### 7.2.2 Exported identifiers An identifier prefixed with `pub` in its declaration is _exported_ and visible to importing modules. See [Clause 18](18-modules.md). ### 7.2.3 Blank identifier The underscore `_` used alone as a pattern is the _blank identifier_. It matches any value and discards it. ```ori let _ = compute(); // discard result match x { _ -> 0 } // match anything ``` ## 7.3 Keywords > **Grammar:** See [grammar.ebnf](grammar.md) § Keywords for the complete keyword listing. ### 7.3.1 Reserved Reserved keywords shall not be used as identifiers except in member position (after `.`). The reserved keywords are: ``` as break continue def div do else extend extension extern false for if impl in let loop match Never pub self Self suspend tests then trait true type unsafe use uses void where while with yield ``` **Exception:** In _member position_ (after `.`), any keyword may be used as a field or method name. The `.` prefix provides unambiguous context, so `x.then(y)` is a method call, not part of an `if`/`then` expression. See [grammar.ebnf](grammar.md) § `member_name`. ### 7.3.2 Reserved (future) The following keywords are reserved for future language features. Their use as identifiers is a compile-time error with an informative message: ``` asm inline static union view ``` ### 7.3.3 Context-sensitive Context-sensitive keywords are recognized as keywords only in specific syntactic positions. Outside those positions, they are valid identifiers. See [grammar.ebnf](grammar.md) § Keywords for the complete listing and position rules. ### 7.3.4 Built-in names Built-in names are reserved in call position (when followed by `(`). Outside call position they may be used as variable names. See [Annex C](annex-c-built-in-functions.md) for semantics. ## 7.4 Operators > **Rules:** See [Annex B](operator-rules.md) for operator semantics. ### 7.4.1 Precedence Operators are listed from highest to lowest precedence: | Prec | Operators | Associativity | |------|-----------|---------------| | 1 | `.` `[]` `()` `?` `as` `as?` | Left | | 2 | `**` | Right | | 3 | `!` `-` `~` (unary) | Right | | 4 | `*` `/` `%` `div` `@` | Left | | 5 | `+` `-` | Left | | 6 | `<<` `>>` | Left | | 7 | `..` `..=` | Non-associative | | 8 | `<` `>` `<=` `>=` | Non-associative | | 9 | `==` `!=` | Non-associative | | 10 | `&` | Left | | 11 | `^` | Left | | 12 | `\|` | Left | | 13 | `&&` | Left | | 14 | `\|\|` | Left | | 15 | `??` | Right | | 16 | `\|>` | Left | NOTE Range (`..`, `..=`), comparison (`<`, `>`, `<=`, `>=`), and equality (`==`, `!=`) operators are non-associative: `a == b == c` is a compile-time error rather than chaining. ### 7.4.2 Short-circuit evaluation The logical operators `&&` and `||` use _short-circuit evaluation_: the right operand is evaluated only if the left operand does not determine the result. - `a && b`: if `a` is `false`, the result is `false` and `b` is not evaluated. - `a || b`: if `a` is `true`, the result is `true` and `b` is not evaluated. The null coalescing operator `??` also short-circuits: `a ?? b` evaluates `b` only if `a` is `None`. ### 7.4.3 Compound assignment operators Compound assignment operators combine a binary operation with assignment: ``` += -= *= /= %= **= @= &= |= ^= <<= >>= &&= ||= ``` A compound assignment `x op= y` desugars to `x = x op y` at the parser level. The target `x` shall be a mutable binding (not `$`-prefixed). Compound assignments are statements, not expressions. The operators `&&=` and `||=` preserve short-circuit semantics: `x &&= y` evaluates `y` only if `x` is `true`. ## 7.5 Delimiters The following characters serve as delimiters: | Delimiter | Name | Usage | |-----------|------|-------| | `(` `)` | Parentheses | Grouping, function parameters, tuples | | `[` `]` | Brackets | List literals, indexing, fixed-capacity | | `{` `}` | Braces | Blocks, struct/map literals, interpolation | | `,` | Comma | Element separator | | `:` | Colon | Type annotation, named argument, map entry | | `.` | Dot | Member access, module path | | `;` | Semicolon | Statement terminator | ## 7.6 Sigils Sigils are single-character prefixes with specific meanings: | Sigil | Purpose | Example | |-------|---------|---------| | `@` | Function declaration | `@main ()` | | `$` | Immutable binding | `let $timeout = 30s;` | The `@` sigil marks a function declaration. It is required before function names at declaration sites and optional at call sites. The `$` sigil marks a binding as immutable. It appears at the definition site, import site, and all usage sites. See [Clause 13](13-variables.md) for details. ## 7.7 Literals A _literal_ represents a fixed value of a specific type in source code. ### 7.7.1 Integer literals An _integer literal_ is a sequence of digits representing a value of type `int` (64-bit signed integer). ```ebnf int_lit = decimal_lit | hex_lit | binary_lit . decimal_lit = "0" | non_zero_digit { [ "_" ] digit } . non_zero_digit = "1" … "9" . hex_lit = "0" ( "x" | "X" ) hex_digit { [ "_" ] hex_digit } . binary_lit = "0" ( "b" | "B" ) bin_digit { [ "_" ] bin_digit } . ``` Leading zeros in decimal literals are not permitted. `007` is a compile-time error; write `7` instead. The single digit `0` is valid. There is no octal literal prefix. Digit separators (`_`) may appear between digits for readability. The following restrictions apply: - A separator shall not appear at the beginning of the digit sequence (after the prefix, if any). - A separator shall not appear at the end of the digit sequence. - Two adjacent separators shall not appear. EXAMPLE Valid integer literals: `42`, `1_000_000`, `0xFF`, `0xFF_FF`, `0b1010_0101`, `0`. EXAMPLE The following are not valid: `007` (leading zero), `42_` (trailing separator), `4__2` (adjacent separators), `0x` (no digits after prefix), `0b` (no digits after prefix). An integer literal shall represent a value in the range 0 to 2^63 − 1 (9 223 372 036 854 775 807). A literal value outside this range is a compile-time error. NOTE The minimum `int` value (−2^63) cannot be written as a literal because the positive value 2^63 exceeds the literal range. It is available as the associated constant `int.min`. Both uppercase and lowercase hex digits are accepted: `0xAB`, `0xab`, and `0xAb` are equivalent. ### 7.7.2 Float literals A _float literal_ is a decimal representation of a value of type `float` (IEEE 754 binary64). ```ebnf float_lit = decimal_digits "." decimal_digits [ exponent ] | decimal_digits exponent . exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits . decimal_digits = digit { [ "_" ] digit } . ``` A float literal shall have at least one digit before and after the decimal point. Leading-dot (`.5`) and trailing-dot (`5.`) forms are not permitted. Digit separators follow the same rules as integer literals: `1_000.000_001` is valid. A float literal that overflows the representable range of IEEE 754 binary64 is a compile-time error. NOTE The special values positive infinity, negative infinity, and NaN are not literals. They are accessed via associated constants: `float.inf`, `float.neg_inf`, `float.nan`. Additional constants are `float.max`, `float.min` (smallest positive normal), and `float.epsilon` (machine epsilon). EXAMPLE Valid float literals: `3.14`, `2.5e-8`, `1_000.0`, `1.0e10`, `6.022E23`. EXAMPLE The following are not valid: `.5` (no leading digit), `5.` (no trailing digit), `1e` (no exponent digits), `0x1.0p10` (no hex floats). ### 7.7.3 String literals A _string literal_ represents a value of type `str` (UTF-8 encoded text). String literals are delimited by double quotes (`"`). ```ebnf string_lit = '"' { string_char | escape_seq } '"' . string_char = unicode_char - '"' - '\' - newline . ``` Regular strings do not support interpolation. Braces `{` and `}` are literal characters in regular strings. A string literal shall not contain an unescaped newline. Multi-line text shall use `\n` escape sequences or template strings (7.7.4). #### 7.7.3.1 Escape sequences The following escape sequences are recognized in string literals, template strings, and character literals: | Escape | Unicode | Name | |--------|---------|------| | `\\` | U+005C | Backslash | | `\"` | U+0022 | Double quote | | `\n` | U+000A | Newline (line feed) | | `\t` | U+0009 | Horizontal tab | | `\r` | U+000D | Carriage return | | `\0` | U+0000 | Null | | `\u{`*H*`}` | U+*HHHH* | Unicode code point | | `\xHH` | U+00*HH* | Hex byte value | The `\xHH` escape specifies a value using exactly two hexadecimal digits (case-insensitive). In string and char literal contexts, the value shall be in the ASCII range (`\x00`–`\x7F`); values `\x80`–`\xFF` are a compile-time error. In byte literal contexts (7.7.6), the full range `\x00`–`\xFF` is valid. This table is exhaustive. Any other `\`-prefixed sequence is a compile-time error. NOTE Legacy escape sequences (`\a`, `\b`, `\f`, `\v`) are not supported. Use `\u{7}` for bell (U+0007), `\u{8}` for backspace (U+0008), etc. #### 7.7.3.2 Unicode escape sequences A _unicode escape sequence_ `\u{`*H*`}` represents a single Unicode code point. The braces contain 1 to 6 hexadecimal digits (case-insensitive). The value shall be a valid Unicode scalar value: U+0000 through U+D7FF or U+E000 through U+10FFFF. Surrogate code points U+D800 through U+DFFF are not valid and produce a compile-time error. EXAMPLE `\u{1F600}` (grinning face), `\u{0041}` ('A'), `\u{7}` (bell). EXAMPLE `\u{D800}` is an error (surrogate code point). ### 7.7.4 Template strings A _template string_ is delimited by backticks (`` ` ``) and supports expression interpolation. ```ebnf template_lit = '`' { template_char | escape_seq | template_escape | interpolation } '`' . template_char = unicode_char - '`' - '\' - '{' - '}' - newline . template_escape = "{{" | "}}" . interpolation = '{' expression [ ':' format_spec ] '}' . ``` Interpolated expressions are enclosed in `{` `}`. Each interpolated expression shall implement the `Printable` trait (or `Formattable` if a format specifier is present). Literal braces within template strings are written as `{{` (left brace) and `}}` (right brace). Literal backticks are written as `` \` ``. The standard escape sequences (see 7.7.3.1) are valid in template strings. Template strings may span multiple lines. Whitespace, including newlines, is preserved exactly as written. Nested template strings are valid. An interpolation `{...}` may contain any expression, including another template string. EXAMPLE ```ori let name = "World"; `Hello, {name}!` // "Hello, World!" `{value:.2}` // 2 decimal places `{count:05}` // zero-pad to 5 digits `outer {`inner {x}`}` // nested template strings ``` ### 7.7.5 Character literals A _character literal_ represents a single Unicode scalar value of type `char`. ```ebnf char_lit = "'" ( char_char | char_escape ) "'" . char_char = unicode_char - "'" - '\' - newline . char_escape = escape_seq | "\'" . ``` A character literal shall contain exactly one character or escape sequence. The following are compile-time errors: - Empty character literal: `''` - Multi-character literal: `'ab'` - Surrogate code point (via unicode escape): `'\u{D800}'` - Hex escape out of ASCII range: `'\x80'` through `'\xFF'` The escape sequence `\'` (single quote) is valid in character literals but not in string or template literals. The `\xHH` escape is valid in character literals but restricted to the ASCII range (`\x00`–`\x7F`). Values `\x80`–`\xFF` are a compile-time error; use `\u{HH}` for code points above U+007F. EXAMPLE Valid character literals: `'a'`, `'\n'`, `'\u{1F600}'`, `'\0'`, `'\''`, `'\x41'`. ### 7.7.6 Byte literals A _byte literal_ represents an unsigned 8-bit integer value of type `byte`. > **Grammar:** See [Annex A](grammar.md) §A.LEXICAL_GRAMMAR ```ebnf byte_lit = "b'" ( byte_char | byte_escape ) "'" . byte_char = ascii_char - "'" - "\" . byte_escape = "\\" | "\'" | "\n" | "\t" | "\r" | "\0" | "\x" hex hex . ``` A byte literal is prefixed with `b` and shall contain exactly one ASCII character or escape sequence. The type is `byte`. Unescaped characters in byte literals shall be printable ASCII (U+0020–U+007E), excluding the delimiter `'` and the escape introducer `\`. Non-ASCII characters are a compile-time error. The `\xHH` escape specifies a byte value from 0x00 to 0xFF using exactly two hexadecimal digits. The full range is valid in byte literals (unlike char literals, which restrict to `\x00`–`\x7F`). The escapes `\u{...}` (Unicode) and `\"` (double quote) are not valid in byte literals. Unicode escapes are not meaningful for bytes (which are 8-bit values), and double quotes do not need escaping since they are not the delimiter. EXAMPLE Valid byte literals: `b'a'`, `b'\n'`, `b'\0'`, `b'\x1B'`, `b'\xFF'`. EXAMPLE Invalid byte literals: `b'\u{41}'` (error: unicode escape in byte literal), `b'é'` (error: non-ASCII character). ### 7.7.7 Boolean literals The boolean literals are `true` and `false`. They have type `bool`. ### 7.7.8 Duration literals A _duration literal_ represents a time span of type `Duration`. The internal representation is a 64-bit signed integer count of nanoseconds. ```ebnf duration_lit = duration_number duration_suffix . duration_number = decimal_lit | decimal_lit "." digit { digit } . duration_suffix = "ns" | "us" | "ms" | "s" | "m" | "h" . ``` Digit separators are permitted in the numeric part: `1_000ms` is valid. The suffixes denote the following units: | Suffix | Unit | Nanoseconds | |--------|------|-------------| | `ns` | Nanoseconds | 1 | | `us` | Microseconds | 1 000 | | `ms` | Milliseconds | 1 000 000 | | `s` | Seconds | 1 000 000 000 | | `m` | Minutes | 60 000 000 000 | | `h` | Hours | 3 600 000 000 000 | Decimal syntax is compile-time sugar using integer arithmetic. The result shall be a whole number of nanoseconds; otherwise it is a compile-time error. EXAMPLE `0.5s` = 500 000 000 ns (valid). `1.5ms` = 1 500 000 ns (valid). `1.5ns` is an error (0.5 nanoseconds is not representable). The maximum duration is limited by the `i64` range of nanoseconds, approximately ±292 years. ### 7.7.9 Size literals A _size literal_ represents a quantity of data of type `Size`. The internal representation is a 64-bit signed integer count of bytes (non-negative). ```ebnf size_lit = size_number size_suffix . size_number = decimal_lit | decimal_lit "." digit { digit } . size_suffix = "b" | "kb" | "mb" | "gb" | "tb" . ``` Digit separators are permitted in the numeric part: `10_000kb` is valid. Size units use SI prefixes (1000-based, not 1024-based): | Suffix | Unit | Bytes | |--------|------|-------| | `b` | Bytes | 1 | | `kb` | Kilobytes | 1 000 | | `mb` | Megabytes | 1 000 000 | | `gb` | Gigabytes | 1 000 000 000 | | `tb` | Terabytes | 1 000 000 000 000 | Decimal syntax follows the same rules as duration literals: the result shall be a whole number of bytes. EXAMPLE `1.5kb` = 1 500 bytes (valid). `0.5b` is an error (0.5 bytes is not representable). Negative size literals are compile-time errors. A `Size` value shall be non-negative. ## 7.8 Semicolons Semicolons (`;`) serve as statement terminators inside block expressions. Outside block expressions, newlines terminate top-level declarations. ### 7.8.1 Block semicolons Within a block expression `{ ... }`, semicolons govern statement termination and the block's result value: 1. Every statement in a block shall be terminated by `;`. 2. The last element of a block, if not terminated by `;`, is the _result expression_ — its value becomes the value of the block. 3. If every element in a block is terminated by `;`, the block has type `void`. The following constructs are _statements_ (require `;` when not the last element): - `let` bindings - `use` imports - assignments and compound assignments - expression statements (an expression evaluated for its side effect) EXAMPLE ```ori { let $x = 1; // statement: needs ; let $y = 2; // statement: needs ; x + y // result expression: no ; } // Block has type int, value 3 ``` EXAMPLE ```ori { print(msg: "hello"); print(msg: "world"); } // All elements have ; → block has type void ``` ### 7.8.2 Declaration termination A top-level declaration whose body is an expression (not a block) shall be terminated by `;`. A declaration whose body ends with `}` does not require `;`. EXAMPLE ```ori @add (a: int, b: int) -> int = a + b; // expression body: needs ; @process (x: int) -> int = { let $y = x * 2; y } // block body: no ; type Point = { x: int, y: int } // ends with }: no ; let $MAX = 100; // module-level constant: needs ; ``` ## 7.9 Trailing commas Trailing commas are permitted after the last element in all comma-separated lists: parameter lists, argument lists, list literals, map literals, struct fields, variant payloads, match arms, and generic parameter lists. The formatter inserts trailing commas in multi-line constructs and removes them in single-line constructs. EXAMPLE ```ori type Color = { r: int, g: int, b: int, // trailing comma permitted } ``` ## 7.10 Lexer-parser contract The lexer produces a _flat stream_ of minimal tokens. The parser combines adjacent tokens based on context. ### 7.10.1 Greater-than sequences The lexer produces individual `>` tokens. It never produces `>>`, `>=`, or `>>=` as single tokens. In _expression context_, adjacent tokens form compound operators: - `>` followed immediately by `>` (no whitespace) → right shift `>>` - `>` followed immediately by `=` (no whitespace) → greater-or-equal `>=` In _type context_, `>` closes a generic parameter list. ```ori // Each > is a separate token — nested generics parse correctly let x: Result, str> = Ok(Ok(1)); // In expressions, >> is right shift let y = 8 >> 2; ``` ### 7.10.2 Token classification The lexer classifies tokens by their first character: - Digit → integer or float literal (or duration/size suffix) - Letter or underscore → identifier or keyword - `"` → string literal - `` ` `` → template string literal - `'` → character literal - Operator character → operator Reserved keywords take precedence over identifiers: the sequence `let` is always the keyword `let`, never the identifier `let`. ## 7.11 Disambiguation ### 7.11.1 Struct literals An uppercase identifier followed by `{` is interpreted as a struct literal in expression context. In `if` condition context, a struct literal is not permitted because the `{` would be ambiguous with a block. ```ori // Struct literal in expression — valid let p = Point { x: 1, y: 2 }; // In if condition — error: struct literal not allowed if Point { x: 1, y: 2 }.valid then ... // error // Use parentheses to disambiguate if (Point { x: 1, y: 2 }).valid then ... // OK // In then-branch — valid: no ambiguity if condition then Point { x: 1, y: 2 } else default ``` NOTE The empty brace pair `{ }` (with whitespace) is parsed as an empty map literal, not an empty block. ### 7.11.2 Soft keywords The following identifiers are keywords only when followed by `(` in expression position: ``` cache catch for match parallel recurse spawn timeout try with ``` The identifier `by` is a keyword only when it follows a range expression (`..` or `..=`): ```ori 0..10 by 2 // by is a keyword (range step) let by = 2; 0..10 by by // first by is keyword, second is variable ``` Outside these contexts, soft keywords may be used as variable names. ### 7.11.3 Parenthesized expressions A parenthesized expression `(...)` is interpreted as: 1. Lambda parameters if followed by `->` and contents match parameter syntax 2. Tuple if it contains a comma: `(a, b)` 3. Unit if empty: `()` 4. Grouped expression otherwise NOTE A single-element tuple is not supported. `(a,)` is parsed as a parenthesized expression (the trailing comma is ignored), not a single-element tuple. ```ori (x) -> x + 1 // lambda with one parameter (x, y) -> x + y // lambda with two parameters (a, b) // tuple () // unit (a + b) * c // grouped expression ``` --- --- title: "Types" description: "Clause 8: Ori Language Specification — Types" order: 8 section: "Language" --- # 8 Types Every value has a type determined at compile time. > **Grammar:** See [grammar.ebnf](grammar.md) § TYPES ## 8.1 Primitive Types | Type | Description | Default | |------|-------------|---------| | `int` | Signed integer (range: -2⁶³ to 2⁶³ - 1) | `0` | | `float` | IEEE 754 double-precision (range: ±1.8 × 10³⁰⁸, ~15-17 digits) | `0.0` | | `bool` | `true` or `false` | `false` | | `str` | UTF-8 string | `""` | | `byte` | Unsigned integer (range: 0 to 255) | `0` | | `char` | Unicode scalar value (U+0000–U+10FFFF, excluding surrogates) | — | | `void` | Unit type, alias for `()` | `()` | | `Never` | Bottom type, uninhabited | — | | `Duration` | Time span (nanoseconds) | `0ns` | | `Size` | Byte count | `0b` | NOTE The ranges above define the _semantic contract_ — the set of values a type can hold and the precision of its operations. The compiler may use a narrower machine representation when it can prove semantic equivalence. See [System Considerations § Representation Optimization](annex-e-system-considerations.md#representation-optimization). `Never` is the _bottom type_ — a type with no values. It represents computations that never complete normally. ### 8.1.1 Never Semantics **Uninhabited:** No value has type `Never`. This makes it useful for: - Functions that never return - Match arms that never execute - Unreachable code paths **Coercion:** `Never` coerces to any type `T`. Since `Never` has no values, the coercion never actually executes — the expression diverges before producing a value. ```ori let x: int = panic(msg: "unreachable"); // Never coerces to int let y: str = unreachable(); // Never coerces to str ``` **Expressions producing Never:** | Expression | Description | |------------|-------------| | `panic(msg:)` | Terminates program | | `todo()`, `todo(reason:)` | Placeholder, terminates | | `unreachable()`, `unreachable(reason:)` | Assertion, terminates | | `break`, `continue` | Loop control (inside loops) | | `expr?` on `Err`/`None` | Early return path | | `loop { }` with no `break` | Infinite loop | **Type inference:** In conditionals, `Never` does not constrain the result type: ```ori let x = if condition then 42 else panic(msg: "fail"); // Type: int (Never coerces to int) ``` If all paths return `Never`, the expression has type `Never`: ```ori let x = if condition then panic(msg: "a") else panic(msg: "b"); // x: Never ``` **Generic contexts:** `Never` can be a type argument: ```ori Result // Can only be Err Result // Can only be Ok Option // Can only be None ``` **Restrictions:** `Never` cannot appear as a struct field type: ```ori type Bad = { value: Never } // error E2019: uninhabited struct field ``` `Never` may appear in sum type variant payloads. Such variants are unconstructable: ```ori type MaybeNever = Value(int) | Impossible(Never); // Only Value(int) values can exist ``` ### 8.1.2 Duration `Duration` represents a span of time with nanosecond precision. Internally stored as a 64-bit signed integer counting nanoseconds (range: approximately ±292 years). **Literal syntax:** | Suffix | Unit | Nanoseconds | |--------|------|-------------| | `ns` | nanoseconds | 1 | | `us` | microseconds | 1,000 | | `ms` | milliseconds | 1,000,000 | | `s` | seconds | 1,000,000,000 | | `m` | minutes | 60,000,000,000 | | `h` | hours | 3,600,000,000,000 | ```ori let timeout = 30s; let delay = 100ms; let precise = 500us; ``` Decimal notation is supported as compile-time sugar: ```ori let half_second = 0.5s; // 500,000,000 nanoseconds let precise = 1.56s; // 1,560,000,000 nanoseconds let quarter_hour = 0.25h; // 15 minutes ``` The decimal portion is converted to an exact integer value using integer arithmetic—no floating-point operations are involved. The result shall be a whole number of nanoseconds; otherwise, it is an error: ```ori // Valid - results in whole nanoseconds 1.5s; // 1,500,000,000 ns 0.001s; // 1,000,000 ns (1ms) 1.123456789s; // 1,123,456,789 ns // Invalid - sub-nanosecond precision 1.5ns; // Error: 1.5 nanoseconds is not a whole number 1.0000000001s; // Error: result has sub-nanosecond precision ``` **Arithmetic:** | Operation | Types | Result | |-----------|-------|--------| | `d1 + d2` | Duration + Duration | Duration | | `d1 - d2` | Duration - Duration | Duration | | `d * n` | Duration * int | Duration | | `n * d` | int * Duration | Duration | | `d / n` | Duration / int | Duration | | `d1 / d2` | Duration / Duration | int (ratio) | | `d1 % d2` | Duration % Duration | Duration (remainder) | | `-d` | -Duration | Duration | Arithmetic panics on overflow. **Conversion methods:** ```ori impl Duration { @nanoseconds (self) -> int; @microseconds (self) -> int; @milliseconds (self) -> int; @seconds (self) -> int; @minutes (self) -> int; @hours (self) -> int; @from_nanoseconds (ns: int) -> Duration; @from_microseconds (us: int) -> Duration; @from_milliseconds (ms: int) -> Duration; @from_seconds (s: int) -> Duration; @from_minutes (m: int) -> Duration; @from_hours (h: int) -> Duration; } ``` Extraction methods truncate toward zero: `90s.minutes()` returns `1`. **Traits:** `Eq`, `Comparable`, `Hashable`, `Clone`, `Debug`, `Printable`, `Default`, `Sendable` ### 8.1.3 Size `Size` represents a byte count. Internally stored as a 64-bit signed integer (non-negative, range: 0 to ~8 exabytes). **Literal syntax:** | Suffix | Unit | Bytes | |--------|------|-------| | `b` | bytes | 1 | | `kb` | kilobytes | 1,000 | | `mb` | megabytes | 1,000,000 | | `gb` | gigabytes | 1,000,000,000 | | `tb` | terabytes | 1,000,000,000,000 | Size uses SI/decimal units (powers of 1000). Programs requiring exact powers of 1024 should use explicit byte counts: `1024b`, `1048576b`. ```ori let buffer = 64kb; let limit = 10mb; let heap = 2gb; ``` Decimal notation is supported as compile-time sugar: ```ori let half_kb = 0.5kb; // 500 bytes let one_and_half_mb = 1.5mb; // 1,500,000 bytes let quarter_gb = 0.25gb; // 250,000,000 bytes ``` The decimal portion is converted to an exact integer value using integer arithmetic. The result shall be a whole number of bytes; otherwise, it is an error: ```ori // Valid - results in whole bytes 1.5kb; // 1,500 bytes 0.001mb; // 1,000 bytes (1kb) // Invalid - sub-byte precision 0.5b; // Error: 0.5 bytes is not a whole number ``` **Arithmetic:** | Operation | Types | Result | |-----------|-------|--------| | `s1 + s2` | Size + Size | Size | | `s1 - s2` | Size - Size | Size (panics if negative) | | `s * n` | Size * int | Size | | `n * s` | int * Size | Size | | `s / n` | Size / int | Size | | `s1 / s2` | Size / Size | int (ratio) | | `s1 % s2` | Size % Size | Size (remainder) | Unary negation (`-`) is not permitted on Size. It is a compile-time error. **Conversion methods:** ```ori impl Size { @bytes (self) -> int; @kilobytes (self) -> int; @megabytes (self) -> int; @gigabytes (self) -> int; @terabytes (self) -> int; @from_bytes (b: int) -> Size; @from_kilobytes (kb: int) -> Size; @from_megabytes (mb: int) -> Size; @from_gigabytes (gb: int) -> Size; @from_terabytes (tb: int) -> Size; } ``` Extraction methods truncate toward zero: `1536kb.megabytes()` returns `1`. **Traits:** `Eq`, `Comparable`, `Hashable`, `Clone`, `Debug`, `Printable`, `Default`, `Sendable` ### 8.1.4 Character Methods The `char` type provides classification and conversion methods. Unicode methods operate on full Unicode; ASCII methods operate on the ASCII subset only. **Unicode classification:** | Method | Description | |--------|-------------| | `is_alphabetic()` | Unicode letter (`L*` categories) | | `is_digit()` | Unicode decimal digit (`Nd` category) | | `is_alphanumeric()` | Letter or digit | | `is_whitespace()` | Unicode whitespace | | `is_uppercase()` | Uppercase letter (`Lu`) | | `is_lowercase()` | Lowercase letter (`Ll`) | | `is_ascii()` | U+0000 through U+007F | | `is_control()` | Control character (`Cc`) | **ASCII classification:** | Method | Description | |--------|-------------| | `is_ascii_alphabetic()` | `a-z`, `A-Z` | | `is_ascii_digit()` | `0-9` | | `is_ascii_alphanumeric()` | `a-z`, `A-Z`, `0-9` | | `is_ascii_whitespace()` | space, tab, newline, CR, VT, FF | | `is_ascii_uppercase()` | `A-Z` | | `is_ascii_lowercase()` | `a-z` | | `is_ascii_hex_digit()` | `0-9`, `a-f`, `A-F` | | `is_ascii_punctuation()` | ASCII punctuation characters | | `is_ascii_control()` | 0x00 through 0x1F and 0x7F | ASCII classification methods return `false` for any `char` outside the ASCII range. **Conversion methods:** | Method | Returns | Description | |--------|---------|-------------| | `to_ascii_uppercase()` | `char` | Uppercase if ASCII letter, else self | | `to_ascii_lowercase()` | `char` | Lowercase if ASCII letter, else self | | `to_digit(radix: int)` | `Option` | Digit value in given radix (2..=36); panics on invalid radix | ### 8.1.5 Byte Methods The `byte` type provides ASCII classification and conversion methods. All classification methods return `false` for byte values above 127 (except `is_ascii()` which tests for this condition). Short aliases are provided for conciseness (e.g., `is_alpha()` for `is_ascii_alpha()`), since `byte` values have no Unicode semantics. | Full Method | Short Alias | Description | |-------------|-------------|-------------| | `is_ascii()` | — | 0x00 through 0x7F | | `is_ascii_alpha()` | `is_alpha()` | `a-z`, `A-Z` | | `is_ascii_digit()` | `is_digit()` | `0-9` | | `is_ascii_alphanumeric()` | `is_alnum()` | `a-z`, `A-Z`, `0-9` | | `is_ascii_whitespace()` | `is_whitespace()` | space, tab, newline, CR, VT, FF | | `is_ascii_uppercase()` | `is_upper()` | `A-Z` | | `is_ascii_lowercase()` | `is_lower()` | `a-z` | | `is_ascii_hex_digit()` | `is_hex_digit()` | `0-9`, `a-f`, `A-F` | | `is_ascii_punctuation()` | — | ASCII punctuation characters | | `is_ascii_control()` | — | 0x00 through 0x1F and 0x7F | **Conversion methods:** | Method | Returns | Description | |--------|---------|-------------| | `to_ascii_uppercase()` | `byte` | Uppercase if ASCII letter, else self | | `to_ascii_lowercase()` | `byte` | Lowercase if ASCII letter, else self | | `to_digit(radix: int)` | `Option` | Digit value in given radix (2..=36); panics on invalid radix | ### 8.1.6 String Byte Access The `str` type provides methods for accessing the underlying UTF-8 byte representation. | Method | Returns | Description | |--------|---------|-------------| | `as_bytes()` | `[byte]` | Zero-copy view via seamless slice | | `to_bytes()` | `[byte]` | Independent copy of UTF-8 bytes | | `byte_len()` | `int` | Number of UTF-8 bytes (O(1)) | `as_bytes()` returns a `[byte]` that shares the underlying allocation with the source `str` via seamless slicing. COW semantics apply — modifying the `[byte]` triggers a copy, leaving the original `str` unaffected. If the source `str` is itself a seamless slice (e.g., from `substring()`), `as_bytes()` produces a single-level `[byte]` view (no nesting). **Associated functions on `str`:** | Function | Returns | Description | |----------|---------|-------------| | `str.from_utf8(bytes:)` | `Result` | Validates UTF-8 encoding | | `str.from_utf8_unchecked(bytes:)` | `str` | Skips validation; requires `unsafe` | `from_utf8_unchecked` with invalid UTF-8 has unspecified but memory-safe behavior — the program may panic or produce garbled output, but shall never cause memory corruption. ## 8.2 Compound Types ### 8.2.1 List ``` [T] ``` Ordered, homogeneous collection. Heap-allocated with dynamic size. ### 8.2.2 Fixed-Capacity List ``` [T, max N] ``` Ordered, homogeneous collection with compile-time maximum capacity `N`. Stored inline (not heap-allocated). Length is dynamic at runtime (0 to N elements). `N` shall be a compile-time constant: a positive integer literal or a `$` constant binding. ```ori let buffer: [int, max 10] = []; // Empty, capacity 10 let coords: [int, max 3] = [1, 2, 3]; // Full, capacity 3 ``` **Subtype relationship:** `[T, max N]` is a subtype of `[T]`. A fixed-capacity list can be passed where a dynamic list is expected. The capacity limit is retained even when viewed as `[T]`. **Methods:** | Method | Return | Description | |--------|--------|-------------| | `.capacity()` | `int` | Compile-time capacity N | | `.is_full()` | `bool` | `len(self) == capacity` | | `.remaining()` | `int` | `capacity - len(self)` | | `.push(item: T)` | `void` | Add element; panic if full | | `.try_push(item: T)` | `bool` | Add element; return false if full | | `.push_or_drop(item: T)` | `void` | Drop item if full | | `.push_or_oldest(item: T)` | `void` | Remove index 0 if full, push to end | | `.to_dynamic()` | `[T]` | Convert to heap-allocated list | **Conversion from dynamic list:** ```ori let dynamic: [int] = [1, 2, 3]; let fixed: [int, max 10] = dynamic.to_fixed<10>(); // Panic if len > 10 let maybe: Option<[int, max 10]> = dynamic.try_to_fixed<10>(); ``` **Trait implementations:** Fixed-capacity lists implement the same traits as regular lists (`Eq`, `Hashable`, `Comparable`, `Clone`, `Debug`, `Printable`, `Sendable`, `Iterable`, `DoubleEndedIterator`, `Collect`) with the same constraints. ### 8.2.3 Map ``` {K: V} ``` Key-value pairs. Keys shall implement `Eq` and `Hashable`. ### 8.2.4 Set ``` Set ``` Unordered unique elements. Elements shall implement `Eq` and `Hashable`. ### 8.2.5 Tuple ``` (T1, T2, ...) () ``` Fixed-size heterogeneous collection. `()` is the unit value. Elements are accessed by zero-based numeric index: ```ori let point = (3, 4); point.0; // 3 point.1; // 4 ``` Tuples also support destructuring in `let` and `match` patterns: ```ori let (x, y) = point; ``` ### 8.2.6 Function ``` (T1, T2) -> R ``` ### 8.2.7 Range ``` Range ``` Produced by `..` (exclusive) and `..=` (inclusive). Bounds shall be `Comparable`. ```ori 0..10; // 0 to 9 0..=10; // 0 to 10 ``` ## 8.3 Generic Types Type parameters in angle brackets: ```ori Option; Result; type Pair = { first: T, second: T } ``` ### 8.3.1 Const Generic Parameters A _const generic parameter_ is a compile-time constant value (not a type) that parameterizes a type or function. Const generic parameters use the `$` sigil followed by a type annotation: ```ori @swap_ends (items: [T, max N]) -> [T, max N] = ...; type RingBuffer = { data: [T, max N], head: int, tail: int } ``` **Allowed const types:** `int`, `bool` Const generic parameters can be used wherever a compile-time constant is expected, including: - Fixed-capacity list capacities: `[T, max N]` - Const expressions in type positions - Const bounds in where clauses ```ori // Const bound in where clause @non_empty_array<$N: int> () -> [int, max N] where N > 0 = ...; ``` #### Default Values Const generics can have default values: ```ori @buffer<$N: int = 64> () -> [byte, max N] = ...; buffer(); // Uses N = 64 buffer<128>(); // Overrides to N = 128 type Vector = [T, max N]; Vector // 3D vector Vector // 4D vector ``` Default const values shall be: 1. Compile-time constant expressions 2. Valid for any bounds on the parameter ```ori @sized<$N: int = 10> () where N > 0 // OK: 10 > 0 = ...; @bad<$N: int = 0> () where N > 0 // ERROR: default value 0 violates bound = ...; ``` #### Parameter Ordering When mixing type and const generic parameters, either ordering is allowed: ```ori @example (...); // Type first (preferred) @example<$N: int, T> (...); // Const first (allowed) @example (...); // Interleaved (allowed) ``` **Style recommendation**: Place type parameters before const generics for consistency. #### Instantiation **Explicit instantiation:** ```ori zeros<10>(); // [int, max 10] replicate(value: "hi"); // [str, max 5] ``` **Inferred instantiation:** When possible, const generics are inferred from context: ```ori let buffer: [int, max 100] = zeros(); // N = 100 inferred ``` **Partial inference:** Type parameters can be inferred while const generics are explicit: ```ori let items = replicate<_, 5>(value: "hello"); // T = str inferred, N = 5 explicit ``` #### Monomorphization Each unique combination of const generic values produces a distinct monomorphized function or type: ```ori zeros<5>(); // Generates zeros_5 zeros<10>(); // Generates zeros_10 // These are distinct types: let a: [int, max 5] = ...; let b: [int, max 10] = a; // ERROR: type mismatch ``` The compiler may warn for excessive instantiations that impact compile time or binary size. #### Const Expressions in Types Const generics enable arithmetic in type positions: ```ori @double_capacity (items: [T, max N]) -> [T, max N * 2] = ...; @halve (items: [T, max N]) -> [T, max N / 2] where N > 0 where N % 2 == 0 = ...; ``` Allowed arithmetic operations in type positions: - Addition: `N + M`, `N + 1` - Subtraction: `N - M`, `N - 1` - Multiplication: `N * M`, `N * 2` - Division: `N / M`, `N / 2` (truncating) - Modulo: `N % M` - Bitwise: `N & M`, `N | M`, `N ^ M`, `N << M`, `N >> M` ### 8.3.2 Const Bounds A _const bound_ constrains the values a const generic parameter may take. Const bounds appear in `where` clauses and are checked at compile time. **Allowed operators in const bounds:** | Category | Operators | |----------|-----------| | Comparison | `==`, `!=`, `<`, `<=`, `>`, `>=` | | Logical | `&&`, `\|\|`, `!` | | Arithmetic | `+`, `-`, `*`, `/`, `%` | | Bitwise | `&`, `\|`, `^`, `<<`, `>>` | ```ori where N > 0 // Simple bound where N >= 1 && N <= 100 // Compound bound where N % 2 == 0 // Divisibility where N & (N - 1) == 0 // Power of two (bitwise) where A || B // Bool parameters ``` Multiple `where` clauses are implicitly combined with `&&`: ```ori where R > 0 where C > 0 // equivalent to: where R > 0 && C > 0 ``` **Evaluation timing:** - When concrete values are known at the call site, bounds are checked immediately - When values depend on outer const parameters, checking is deferred to monomorphization **Constraint propagation:** When calling a function with const bounds, the caller's bounds shall _imply_ the callee's bounds. The compiler performs linear arithmetic implication checking: ```ori @inner<$N: int> () -> [int, max N] where N >= 10 = ...; @outer<$M: int> () -> [int, max M] where M >= 20 // M >= 20 implies M >= 10 = inner(); // OK ``` **Overflow handling:** Arithmetic overflow during const bound evaluation is a compile-time error (E1033). Const bound arithmetic uses 64-bit signed integers. **Instance methods with const generics:** ```ori // Conversion methods on [T] [T].to_fixed<$N: int>() -> [T, max N]; [T].try_to_fixed<$N: int>() -> Option<[T, max N]>; ``` ## 8.4 Built-in Types ``` type Option = Some(T) | None; type Result = Ok(T) | Err(E); type Ordering = Less | Equal | Greater; type Error = { message: str, source: Option } // trace field internal type TraceEntry = { function: str, file: str, line: int, column: int } type NurseryErrorMode = CancelRemaining | CollectAll | FailFast; ``` ### 8.4.1 Ordering The `Ordering` type represents the result of comparing two values. | Variant | Meaning | |---------|---------| | `Less` | Left operand is less than right | | `Equal` | Left operand equals right | | `Greater` | Left operand is greater than right | #### Ordering Methods ```ori impl Ordering { @is_less (self) -> bool; @is_equal (self) -> bool; @is_greater (self) -> bool; @is_less_or_equal (self) -> bool; @is_greater_or_equal (self) -> bool; @reverse (self) -> Ordering; @then (self, other: Ordering) -> Ordering; @then_with (self, f: () -> Ordering) -> Ordering; } ``` The `then` method chains comparisons for lexicographic ordering. It returns `self` unless `self` is `Equal`, in which case it returns `other`. The `then_with` method is a lazy variant that only evaluates its argument when `self` is `Equal`. ```ori // Lexicographic comparison of (a1, a2) with (b1, b2) compare(left: a1, right: b1).then(other: compare(left: a2, right: b2)); // Lazy version — second comparison only evaluated if first is Equal compare(left: a1, right: b1).then_with(f: () -> compare(left: a2, right: b2)); ``` #### Ordering Traits `Ordering` implements: `Eq`, `Comparable`, `Clone`, `Debug`, `Printable`, `Hashable`, `Default`. The `Default` value is `Equal`. The `Comparable` ordering is: `Less < Equal < Greater`. ## 8.5 Channel Types Role-based channel types enforce producer/consumer separation at compile time. ```ori type Producer // Can only send type Consumer // Can only receive type CloneableProducer // Producer that implements Clone type CloneableConsumer // Consumer that implements Clone ``` ### 8.5.1 Channel Constructors ```ori // One-to-one (exclusive, fastest) @channel (buffer: int) -> (Producer, Consumer); // Fan-in (many-to-one, producer cloneable) @channel_in (buffer: int) -> (CloneableProducer, Consumer); // Fan-out (one-to-many, consumer cloneable) @channel_out (buffer: int) -> (Producer, CloneableConsumer); // Many-to-many (both cloneable) @channel_all (buffer: int) -> (CloneableProducer, CloneableConsumer); ``` ### 8.5.2 Producer Methods ```ori impl Producer { @send (self, value: T) -> void uses Suspend; // Consumes value @close (self) -> void; @is_closed (self) -> bool; } ``` Sending a value transfers ownership. The value cannot be used after send. ### 8.5.3 Consumer Methods ```ori impl Consumer { @receive (self) -> Option uses Suspend; @is_closed (self) -> bool; } impl Consumer: Iterable { type Item = T; } ``` `receive` returns `None` when the channel is closed and empty. ### 8.5.4 Cloneability `CloneableProducer` and `CloneableConsumer` implement `Clone`. Regular `Producer` and `Consumer` do not. ```ori let (p, c) = channel(buffer: 10); // p.clone() // error: Producer does not implement Clone let (p, c) = channel_in(buffer: 10); let p2 = p.clone(); // OK: CloneableProducer implements Clone ``` ## 8.6 User-Defined Types > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (type_def) ### 8.6.1 Struct ```ori type Point = { x: int, y: int } ``` ### 8.6.2 Sum Type ```ori type Status = Pending | Running | Done | Failed(reason: str); ``` ### 8.6.3 Newtype ```ori type UserId = int; ``` A _newtype_ creates a distinct nominal type that wraps an existing type. **Construction:** Newtypes use their type name as a constructor: ```ori type UserId = int; let id = UserId(42); ``` Literals cannot directly become newtypes: ```ori let id: UserId = 42; // error: expected UserId, found int ``` **Underlying Value Access:** The underlying value is accessed via `.inner`: ```ori let id = UserId(42); let raw: int = id.inner; ``` The `.inner` accessor is always public, regardless of the newtype's visibility. The type-safety boundary is at construction, not access. **No Trait Inheritance:** Newtypes do not automatically inherit traits from their underlying type: ```ori type UserId = int; let a = UserId(1); let b = UserId(2); a == b; // error: UserId does not implement Eq ``` Derive traits explicitly: ```ori #derive(Eq, Hashable, Clone, Debug) type UserId = int; ``` **No Method Inheritance:** Newtypes do not expose the underlying type's methods: ```ori type Email = str; let email = Email("user@example.com"); email.len(); // error: Email has no method len email.inner.len(); // OK ``` **Generic Newtypes:** ```ori type NonEmpty = [T]; impl NonEmpty { @first (self) -> T = self.inner[0]; } ``` **Performance:** Newtypes have zero runtime overhead. They share the same memory layout as their underlying type; the compiler erases the wrapper. ### 8.6.4 Derive ```ori #derive(Eq, Hashable, Clone) type Point = { x: int, y: int } ``` The `#derive` attribute generates trait implementations automatically for user-defined types. **Derivable Traits:** | Trait | Struct | Sum Type | Newtype | Requirement | |-------|--------|----------|---------|-------------| | `Eq` | Yes | Yes | Yes | All fields/underlying implement `Eq` | | `Hashable` | Yes | Yes | Yes | All fields/underlying implement `Hashable` | | `Comparable` | Yes | Yes | Yes | All fields/underlying implement `Comparable` | | `Clone` | Yes | Yes | Yes | All fields/underlying implement `Clone` | | `Default` | Yes | No | Yes | All fields/underlying implement `Default` | | `Debug` | Yes | Yes | Yes | All fields/underlying implement `Debug` | | `Printable` | Yes | Yes | Yes | All fields/underlying implement `Printable` | **Derivation Rules:** - `Eq`: Field-wise equality comparison; newtypes delegate to underlying type - `Hashable`: Combined field hashes using `hash_combine`; warning if derived without `Eq`; newtypes delegate to underlying type - `Comparable`: Lexicographic comparison by field declaration order; sum type variants compare by declaration order; newtypes delegate to underlying type - `Clone`: Field-wise cloning via `.clone()` method; newtypes delegate to underlying type - `Default`: Field-wise default construction; cannot be derived for sum types (ambiguous variant); newtypes delegate to underlying type - `Debug`: Structural representation: `TypeName { field1: value1, field2: value2 }`; newtypes show `TypeName(value)` - `Printable`: Human-readable format: `TypeName(value1, value2)`; newtypes show `TypeName(value)` **Generic Types:** Generic types derive traits conditionally based on type parameter constraints: ```ori #derive(Eq, Clone) type Pair = { first: T, second: T } // Generated: impl Pair: Eq { ... } impl Pair: Clone { ... } ``` **Recursive Types:** Recursive types can derive traits; generated implementations handle recursion correctly. **Non-Derivable Traits:** | Trait | Reason | |-------|--------| | `Iterator` | Requires custom `next` logic | | `Iterable` | Requires custom `iter` logic | | `Into` | Requires custom conversion logic | | `Drop` | Requires custom cleanup logic | | `Sendable` | Automatically derived by compiler | NOTE 1 Types implementing `Printable` automatically implement `Formattable` via blanket implementation. NOTE 2 Multiple `#derive` attributes are equivalent to a single attribute with combined traits. NOTE 3 Derive order does not affect behavior. ## 8.7 Nominal Typing User-defined types are nominally typed. Identical structure does not imply same type. ## 8.8 Trait Objects A trait name used as a type represents "any value implementing this trait": ```ori @display (item: Printable) -> void = print(item.to_str()); let items: [Printable] = [point, user, "hello"]; ``` The compiler determines the dispatch mechanism. Users specify *what* (any Printable), not *how* (vtable vs monomorphization). ### 8.8.1 Trait Object vs Generic Bound | Syntax | Meaning | |--------|---------| | `item: Printable` | Any Printable value (trait object) | | ` item: T` | Generic over Printable types | Use trait objects for heterogeneous collections. Use generics when all elements share a concrete type. ### 8.8.2 Object Safety A trait is _object-safe_ if it can be used as a trait object. Not all traits qualify — some require compile-time type information that is unavailable for trait objects. A trait is object-safe if ALL of the following rules are satisfied: **Rule 1: No `Self` in Return Position** Methods cannot return `Self`: ```ori // NOT object-safe: returns Self trait Clone { @clone (self) -> Self; } // Object-safe: returns fixed type trait Printable { @to_str (self) -> str; } ``` The compiler cannot determine the concrete return type size at runtime. **Rule 2: No `Self` in Parameter Position (Except Receiver)** Methods cannot take `Self` as a parameter (except for the first `self` receiver): ```ori // NOT object-safe: Self as parameter trait Eq { @equals (self, other: Self) -> bool; } // Object-safe: takes trait object trait EqDyn { @equals_any (self, other: EqDyn) -> bool; } ``` The compiler cannot verify that `other` has the same concrete type as `self`. **Rule 3: No Generic Methods** Methods cannot have type parameters: ```ori // NOT object-safe: generic method trait Converter { @convert (self) -> T; } // Object-safe: no generics trait Formatter { @format (self, spec: FormatSpec) -> str; } ``` Generic methods require monomorphization at compile time, but trait objects defer type information to runtime. **Bounded Trait Objects** Trait objects can have additional bounds. All component traits shall be object-safe: ```ori @store (item: Printable + Hashable) -> void; ``` **Error Codes** - `E2024`: Trait is not object-safe (covers all three rules; violation details included in error message) ## 8.9 Clone Trait The `Clone` trait enables explicit value duplication: ```ori trait Clone { @clone (self) -> Self; } ``` `Clone` creates an independent copy of a value. The clone operation: - For value types: returns a copy of the value - For reference types: allocates new memory with refcount 1 - Element-wise recursive: cloning a container clones each element via `.clone()` After cloning, original and clone have independent reference counts. Modifying the clone does not affect the original. ### 8.9.1 Standard Implementations All primitive types implement `Clone`: | Type | Implementation | |------|----------------| | `int`, `float`, `bool`, `str`, `char`, `byte` | Returns copy of self | | `Duration`, `Size` | Returns copy of self | Collections implement `Clone` when their element types implement `Clone`: | Type | Constraint | |------|------------| | `[T]` | `T: Clone` | | `{K: V}` | `K: Clone, V: Clone` | | `Set` | `T: Clone` | | `Option` | `T: Clone` | | `Result` | `T: Clone, E: Clone` | | `(A, B, ...)` | All element types: Clone | ### 8.9.2 Derivable `Clone` is derivable for user-defined types when all fields implement `Clone`: ```ori #derive(Clone) type Point = { x: int, y: int } ``` Derived implementation clones each field. ### 8.9.3 Non-Cloneable Types Some types do not implement `Clone`: - Unique resources (file handles, network connections) - Types with identity where duplicates would be semantically wrong ## 8.10 Drop Trait The `Drop` trait enables custom cleanup when a value's reference count reaches zero: ```ori trait Drop { @drop (self) -> void; } ``` ### 8.10.1 Execution Timing Drop is called when the ARC reference count reaches zero: ```ori { let resource = acquire_resource(); // refcount: 1 use_resource(resource); // refcount may increase } // refcount: 0, drop called ``` For values not shared, drop occurs at scope exit. Drop is also called on early exit (via `?`, `break`, or panic). ### 8.10.2 Drop Order Values are dropped in reverse declaration order (LIFO): ```ori { let a = Resource { name: "a" }; let b = Resource { name: "b" }; let c = Resource { name: "c" }; } // Drop order: c, b, a ``` **Struct fields:** Dropped in reverse declaration order, then the struct. **Collections:** Elements dropped in reverse order (back-to-front). ### 8.10.3 Constraints **No async operations:** Drop cannot perform suspending operations. Drop runs synchronously during stack unwinding. Async operations could deadlock. ```ori impl Connection: Drop { @drop (self) -> void uses Suspend = ...; // error E0980: Drop cannot be async } ``` **Shall return void:** Drop shall return `void`. **Panic during unwind:** If drop panics while another panic is unwinding, the program aborts immediately. ### 8.10.4 Not Derivable `Drop` cannot be derived. Drop behavior is specific to each type; automatic derivation would be either no-op or incorrect. ### 8.10.5 Explicit Drop The `drop_early` function forces drop before scope exit: ```ori @drop_early (value: T) -> void; { let file = open_file(path); let content = read_all(file); drop_early(value: file); // Close immediately // ... continue processing content } ``` ### 8.10.6 Standard Implementations Most types do not need `Drop`: - Primitives: `int`, `float`, `bool`, `str`, `char`, `byte` - Collections: `[T]`, `{K: V}`, `Set` (elements dropped automatically) - Options and Results: `Option`, `Result` (values dropped automatically) Types wrapping external resources typically implement Drop: ```ori impl FileHandle: Drop { @drop (self) -> void = close_file_descriptor(self.fd); } impl Lock: Drop { @drop (self) -> void = release_lock(self.handle); } ``` ### 8.10.7 Error Handling in Drop Drop should handle its own errors. Drop cannot return errors; propagating would require panic, which is dangerous during unwinding: ```ori impl Connection: Drop { @drop (self) -> void = match self.close() { Ok(_) -> () Err(e) -> log(msg: `close failed: {e}`), // Log, don't propagate } } ``` ## 8.11 Conversion Traits Three traits formalize type conversions: ### 8.11.1 Into Trait The `Into` trait represents semantic, lossless type conversion: ```ori trait Into { @into (self) -> T; } ``` **Usage:** ```ori let error: Error = "something went wrong".into(); let f: float = 42.into(); ``` Conversion requires explicit `.into()` method call. Ori does NOT perform implicit conversion. **Standard Implementations:** | From | To | Notes | |------|-----|-------| | `str` | `Error` | Creates Error with message | | `int` | `float` | Lossless widening | | `Set` | `[T]` | Requires `T: Eq + Hashable` | **No Blanket Identity:** There is no blanket `impl T: Into`. Each conversion shall be explicitly implemented. **No Automatic Chaining:** `Into` does not chain automatically. Given `A: Into` and `B: Into`, converting `A` to `C` requires `a.into().into()`. ### 8.11.2 As Trait The `As` trait defines infallible type conversion: ```ori trait As { @as (self) -> T; } ``` The `as` operator desugars to this trait: ```ori 42 as float; // Desugars to: As.as(self: 42); ``` **Standard Implementations:** | From | To | Notes | |------|-----|-------| | `int` | `float` | Widening | | `byte` | `int` | Widening | | `int` | `str` | Formatting | | `float` | `str` | Formatting | | `bool` | `str` | "true" or "false" | | `char` | `str` | Single character string | | `byte` | `str` | Formatting | | `char` | `int` | Codepoint value | ### 8.11.3 TryAs Trait The `TryAs` trait defines fallible type conversion: ```ori trait TryAs { @try_as (self) -> Option; } ``` The `as?` operator desugars to this trait: ```ori "42" as? int; // Desugars to: TryAs.try_as(self: "42"); ``` **Standard Implementations:** | From | To | Notes | |------|-----|-------| | `str` | `int` | Parsing | | `str` | `float` | Parsing | | `str` | `bool` | "true"/"false" only | | `int` | `byte` | Range check (0-255) | | `char` | `byte` | ASCII check (0-127) | | `int` | `char` | Valid codepoint check | ### 8.11.4 Comparison | Mechanism | Fallible | Extensible | Use Case | |-----------|----------|------------|----------| | `as` | No | Yes | Infallible representation changes | | `as?` | Yes | Yes | Parsing, checked conversions | | `Into` | No | Yes | Semantic type conversions | ### 8.11.5 Lossy Conversions Lossy conversions (like `float -> int`) are not supported by `as` or `as?`. Use explicit methods that communicate intent: ```ori 3.7.truncate(); // 3 (toward zero) 3.7.round(); // 4 (nearest) 3.7.floor(); // 3 (toward negative infinity) 3.7.ceil(); // 4 (toward positive infinity) ``` It is a compile-time error to use `as` for a lossy conversion. ### 8.11.6 User-Defined Conversions Types can implement conversion traits: ```ori type UserId = int; impl UserId: As { @as (self) -> str = "user_" + (self.inner as str); } impl str: TryAs { @try_as (self) -> Option = if self.is_empty() || self.len() > 32 then None else Some(Username { value: self }); } ``` ## 8.12 Debug Trait The `Debug` trait provides developer-facing string representation: ```ori trait Debug { @debug (self) -> str; } ``` Unlike `Printable`, which is for user-facing display, `Debug` shows the complete internal structure and is always derivable. ```ori #derive(Debug) type Point = { x: int, y: int } Point { x: 1, y: 2 }.debug(); // "Point { x: 1, y: 2 }" ``` ### 8.12.1 Standard Implementations All primitive types implement `Debug`: | Type | Output Format | |------|---------------| | `int`, `float`, `byte` | Numeric string | | `bool` | `"true"` or `"false"` | | `str` | Quoted with escapes: `"\"hello\""` | | `char` | Quoted with escapes: `"'\\n'"` | | `void` | `"()"` | | `Duration`, `Size` | Human-readable format | Collections implement `Debug` when their element types implement `Debug`: | Type | Output Format | |------|---------------| | `[T]` | `"[1, 2, 3]"` | | `{K: V}` | `"{\"a\": 1, \"b\": 2}"` | | `Set` | `"Set {1, 2, 3}"` | | `Option` | `"Some(42)"` or `"None"` | | `Result` | `"Ok(42)"` or `"Err(\"message\")"` | | `(A, B, ...)` | `"(1, \"hello\")"` | ### 8.12.2 Derivable `Debug` is derivable for user-defined types when all fields implement `Debug`: ```ori #derive(Debug) type Config = { host: str, port: int } Config { host: "localhost", port: 8080 }.debug(); // "Config { host: \"localhost\", port: 8080 }" ``` ### 8.12.3 Manual Implementation Types may implement `Debug` manually for custom formatting: ```ori type SecretKey = { value: [byte] } impl SecretKey: Debug { @debug (self) -> str = "SecretKey { value: [REDACTED] }"; } ``` ## 8.13 Iterator Traits Four traits formalize iteration: ```ori trait Iterator { type Item; @next (self) -> (Option, Self); } trait DoubleEndedIterator: Iterator { @next_back (self) -> (Option, Self); } trait Iterable { type Item; @iter (self) -> impl Iterator where Item == Self.Item; } trait Collect { @from_iter (iter: I) -> Self where I.Item == T; } ``` `Iterator.next()` returns a tuple of the optional value and the updated iterator. This functional approach fits Ori's immutable parameter semantics. **Fused Guarantee:** Once `next()` returns `(None, iter)`, all subsequent calls shall return `(None, _)`. `Range` does not implement `Iterable` due to floating-point precision ambiguity. ### 8.13.1 Standard Implementations | Type | Implements | |------|------------| | `[T]` | `Iterable`, `DoubleEndedIterator`, `Collect` | | `{K: V}` | `Iterable` (not double-ended) | | `Set` | `Iterable`, `Collect` (not double-ended) | | `str` | `Iterable`, `DoubleEndedIterator` | | `Range` | `Iterable`, `DoubleEndedIterator` | | `Option` | `Iterable` | ## 8.14 Sendable Trait The `Sendable` trait marks types that can safely cross task boundaries. ```ori trait Sendable {} ``` `Sendable` is a marker trait with no methods. It is automatically implemented when: 1. All fields are `Sendable` 2. No interior mutability 3. No non-Sendable captured state (for closures) ### 8.14.1 Interior Mutability Interior mutability does not exist in user-defined Ori types. Ori's memory model prohibits shared mutable references, making interior mutability impossible by design. The only types with interior mutability are runtime-provided resources. These wrap OS or runtime state that changes independently of Ori's ownership rules: - File descriptors (kernel-managed state) - Network connections (internal buffers) - Database connections (session state) ### 8.14.2 Manual Implementation `Sendable` cannot be implemented manually. It is automatically derived by the compiler when all conditions are met. This ensures thread safety cannot be circumvented. ```ori impl MyType: Sendable { } // error: cannot implement Sendable manually ``` ### 8.14.3 Standard Implementations | Type | Sendable | |------|----------| | `int`, `float`, `bool`, `str`, `char`, `byte` | Yes | | `Duration`, `Size` | Yes | | `void`, `Never` | Yes | | `[T]` where `T: Sendable` | Yes | | `{K: V}` where `K: Sendable, V: Sendable` | Yes | | `Set` where `T: Sendable` | Yes | | `Option` where `T: Sendable` | Yes | | `Result` where `T: Sendable, E: Sendable` | Yes | | `(T1, T2, ...)` where all `Ti: Sendable` | Yes | | `(T) -> R` where captures are `Sendable` | Yes | | `Producer` where `T: Sendable` | Yes | | `Consumer` where `T: Sendable` | Yes | | `CloneableProducer` where `T: Sendable` | Yes | | `CloneableConsumer` where `T: Sendable` | Yes | ### 8.14.4 Non-Sendable Types | Type | Reason | |------|--------| | `FileHandle` | OS resource with thread affinity | | `Socket` | OS resource, not safely movable | | `DatabaseConnection` | Session state, not safely movable | | `Nursery` | Scoped to specific execution context | User-defined types are not `Sendable` when they contain non-Sendable fields. ### 8.14.5 Closure Sendability The compiler analyzes closure captures to determine Sendability: ```ori let x: int = 10; // int: Sendable let handle: FileHandle = ...; // FileHandle: NOT Sendable let f = () -> x + 1; // f is Sendable let g = () -> handle.read(); // g is NOT Sendable ``` When closures cross task boundaries, the compiler verifies all captures are Sendable: ```ori parallel( tasks: [ () -> process(x), // OK: x is Sendable () -> read(handle), // error: handle is not Sendable ], ); ``` ### 8.14.6 Channel Constraint Channel types require `T: Sendable`: ```ori let (p, c) = channel(buffer: 10); // OK: int is Sendable type Handle = { file: FileHandle } let (p, c) = channel(buffer: 10); // error: Handle is not Sendable ``` ### 8.14.7 Value Trait The `Value` trait marks types that are stored inline, copied by value (bitwise copy), and completely bypass ARC (automatic reference counting). ```ori trait Value: Clone, Eq {} ``` `Value` is a marker trait with no methods. It shall not be manually implemented. The compiler verifies a type satisfies `Value` when all of the following hold: 1. All fields recursively satisfy `Value` 2. The type does not implement `Drop` 3. The type is not recursive (no heap indirection) 4. The type size does not exceed 512 bytes A type that satisfies `Value` automatically satisfies `Clone` (via bitwise copy) and `Sendable`. NOTE A type exceeding 256 bytes but not 512 bytes produces a warning. Types exceeding 512 bytes produce an error. #### Standard Value Types | Type | Value? | |------|--------| | `int`, `float`, `bool`, `char`, `byte`, `void` | Yes | | `Duration`, `Size`, `Ordering` | Yes | | `Never` | Yes (vacuously) | | `str` | No (heap-allocated, reference-counted) | | `[T]`, `{K: V}`, `Set` | No (heap-allocated) | | `(T) -> U` | No (may capture heap-allocated values) | | `Option` where `T: Value` | Yes | | `Result` where `T: Value, E: Value` | Yes | | `Range` where `T: Value` | Yes | | `(T, U, ...)` where all elements are `Value` | Yes | #### Declaration Syntax ```ori type Point: Value, Eq = { x: float, y: float } type Color: Value, Eq, Hashable = { r: byte, g: byte, b: byte, a: byte } type Direction: Value, Eq = North | South | East | West type Meters: Value, Eq = float ``` EXAMPLE ```ori // Valid: all fields are Value type Vec3: Value, Eq = { x: float, y: float, z: float } // Invalid: str is not Value type Entity: Value, Eq = { x: int, name: str } // error[E2040] ``` ## 8.15 Existential Types An _existential type_ written `impl Trait` represents an opaque type that satisfies the specified trait bounds. The concrete type is known to the compiler internally but hidden from callers. > **Grammar:** See [grammar.ebnf](grammar.md) § TYPES (impl_trait_type) ### 8.15.1 Syntax ```ori @make_iterator (items: [int]) -> impl Iterator where Item == int = items.iter(); ``` The grammar for existential types: ```ebnf impl_trait_type = "impl" trait_bounds [ impl_where_clause ] . trait_bounds = type_path { "+" type_path } . impl_where_clause = "where" assoc_constraint { "," assoc_constraint } . assoc_constraint = identifier "==" type . ``` Multiple trait bounds use `+`: ```ori @clonable_iter () -> impl Iterator + Clone where Item == int = ...; ``` ### 8.15.2 Semantics **Opaqueness:** Callers see only the trait interface. The concrete type's fields and methods beyond the trait bounds are inaccessible. ```ori let iter = make_iterator(items: [1, 2, 3]); iter.next(); // OK: Iterator method iter.list; // error: cannot access concrete type's fields ``` **Single Concrete Type:** All return paths shall yield the same concrete type: ```ori // OK: all paths return ListIterator @numbers (flag: bool) -> impl Iterator where Item == int = if flag then [1, 2, 3].iter() else [4, 5, 6].iter(); // error: different concrete types @bad (flag: bool) -> impl Iterator where Item == int = if flag then [1, 2, 3].iter() // ListIterator else (1..10).iter(); // RangeIterator ``` **Static Dispatch:** The compiler monomorphizes each call site. No vtable overhead. ### 8.15.3 Valid Positions `impl Trait` is allowed only in return position: | Position | Allowed | |----------|---------| | Function return | Yes | | Method return | Yes | | Trait method return | Yes | | Argument position | No — use generics | | Struct fields | No — use generics | ```ori // Argument position: use generic parameter @process (iter: I) -> int where I.Item == int = ...; // Struct field: use generic parameter type Container = { iter: I } where I.Item == int ``` ### 8.15.4 Comparison with Trait Objects | Aspect | `impl Trait` | Trait Object (`Trait`) | |--------|--------------|------------------------| | Dispatch | Static (monomorphized) | Dynamic (vtable) | | Size | Concrete type size | Pointer size | | Performance | Better (inlinable) | Vtable overhead | | Flexibility | Single concrete type | Any type at runtime | | Object safety | All traits | Object-safe traits only | Use `impl Trait` when a single concrete type is returned and performance matters. Use trait objects when multiple types may be returned at runtime. ### 8.15.5 Error Codes - `E0810`: `impl Trait` only allowed in return position - `E0811`: All return paths shall have the same concrete type - `E0812`: Concrete type does not implement required trait bounds ## 8.16 Type Inference Types inferred where possible. Required annotations: - Function parameters - Function return types - Type definitions --- --- title: "Properties of Types" description: "Clause 9: Ori Language Specification — Properties of Types" order: 9 section: "Language" --- # 9 Properties of Types Type identity, assignability, and constraints. > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (generics, where_clause) ## 9.1 Type Identity Two types are identical if they have the same definition. **Primitives**: Each primitive is identical only to itself. **Compounds**: Same constructor and pairwise identical type arguments. ``` [int] ≡ [int] [int] ≢ [str] (int, str) ≢ (str, int) ``` **Nominal**: Same type definition, not structural equivalence. ```ori type Point2D = { x: int, y: int } type Vector2D = { x: int, y: int } // Point2D ≢ Vector2D ``` **Generics**: Same definition and pairwise identical arguments. ``` Option ≡ Option Option ≢ Option ``` ## 9.2 Assignability A value of type `S` is assignable to type `T` if: - `S` is identical to `T`, or - `S` implements trait `T` and target is `dyn T` No implicit conversions: ```ori let x: float = 42; // error let x: float = float(42); // OK ``` ## 9.3 Variance Generics are invariant. `Container` is only compatible with `Container`. ## 9.4 Type Constraints ```ori @sort (items: [T]) -> [T] = ...; @process (items: [T], f: (T) -> U) -> [U] where T: Clone, U: Default = ...; ``` ## 9.5 Default Values | Type | Default | |------|---------| | `int` | `0` | | `float` | `0.0` | | `bool` | `false` | | `str` | `""` | | `byte` | `0` | | `void` | `()` | | `Option` | `None` | | `[T]` | `[]` | | `{K: V}` | `{}` | Types implementing `Default` provide `default()` method. ## 9.6 Printable Trait The `Printable` trait provides human-readable string conversion. ```ori trait Printable { @to_str (self) -> str; } ``` `Printable` is required for string interpolation without format specifiers: ```ori let x = 42; `value: {x}` // Calls x.to_str() ``` ### 9.6.1 Standard Implementations | Type | Output | |------|--------| | `int` | `"42"` | | `float` | `"3.14"` | | `bool` | `"true"` or `"false"` | | `str` | Identity | | `char` | Single character string | | `byte` | Numeric string | | `[T]` where `T: Printable` | `"[1, 2, 3]"` | | `Option` where `T: Printable` | `"Some(42)"` or `"None"` | | `Result` where both Printable | `"Ok(42)"` or `"Err(msg)"` | ### 9.6.2 Derivation `Printable` is derivable for user-defined types when all fields implement `Printable`: ```ori #derive(Printable) type Point = { x: int, y: int } Point { x: 1, y: 2 }.to_str() // "Point(1, 2)" ``` Derived implementation creates human-readable format with type name and field values in order. ## 9.7 Formattable Trait The `Formattable` trait provides formatted string conversion with format specifications. ```ori trait Formattable { @format (self, spec: FormatSpec) -> str; } ``` `Formattable` is required for string interpolation with format specifiers: ```ori let n = 42; `hex: {n:x}` // Calls n.format(spec: ...) with Hex format type `padded: {n:08}` // Calls n.format(spec: ...) with width 8, zero-pad ``` ### 9.7.1 FormatSpec Type ```ori type FormatSpec = { fill: Option, align: Option, sign: Option, width: Option, precision: Option, format_type: Option, } type Alignment = Left | Center | Right; type Sign = Plus | Minus | Space; type FormatType = Binary | Octal | Hex | HexUpper | Exp | ExpUpper | Fixed | Percent; ``` These types are in the prelude. ### 9.7.2 Format Spec Syntax Format specifications in template strings use the syntax: ``` [[fill]align][sign][#][0][width][.precision][type] ``` | Component | Syntax | Description | |-----------|--------|-------------| | Fill | Any character | Padding character (default: space) | | Align | `<` `>` `^` | Left, right, center alignment | | Sign | `+` `-` ` ` | Sign display for numbers | | `#` | `#` | Alternate form (prefix for hex, etc.) | | `0` | `0` | Zero-pad (implies right-align) | | Width | Integer | Minimum field width | | Precision | `.N` | Decimal places or max string length | | Type | Letter | Format type (b, o, x, X, e, E, f, %) | ### 9.7.3 Format Types **Integer Types:** | Type | Description | Example | |------|-------------|---------| | `b` | Binary | `42` → `"101010"` | | `o` | Octal | `42` → `"52"` | | `x` | Hex (lowercase) | `255` → `"ff"` | | `X` | Hex (uppercase) | `255` → `"FF"` | **Float Types:** | Type | Description | Example | |------|-------------|---------| | `e` | Scientific (lowercase) | `1234.5` → `"1.2345e+03"` | | `E` | Scientific (uppercase) | `1234.5` → `"1.2345E+03"` | | `f` | Fixed-point (6 decimals) | `1234.5` → `"1234.500000"` | | `%` | Percentage | `0.75` → `"75%"` | **Alternate Form (`#`):** | Type | Without `#` | With `#` | |------|-------------|----------| | `b` | `"101010"` | `"0b101010"` | | `o` | `"52"` | `"0o52"` | | `x` | `"ff"` | `"0xff"` | | `X` | `"FF"` | `"0xFF"` | ### 9.7.4 Standard Implementations | Type | Behavior | |------|----------| | `int` | Supports b, o, x, X format types; sign and alternate form | | `float` | Supports e, E, f, % format types; precision and sign | | `str` | Width, alignment, fill; precision truncates | | `bool` | Width and alignment only | | `char` | Width and alignment only | ### 9.7.5 Blanket Implementation All `Printable` types have a blanket `Formattable` implementation: ```ori impl T: Formattable { @format (self, spec: FormatSpec) -> str = { let base = self.to_str(); apply_format(s: base, spec: spec) } } ``` This applies width, alignment, and fill. Type-specific formatting (binary, hex, etc.) is only available for types that implement `Formattable` directly. ### 9.7.6 Custom Implementation User types may implement `Formattable` for custom formatting: ```ori type Money = { cents: int } impl Money: Formattable { @format (self, spec: FormatSpec) -> str = { let dollars = self.cents / 100; let cents = self.cents % 100; let base = `{dollars}.{cents:02}`; apply_alignment(s: base, spec: spec) } } ``` Newtypes can delegate to their inner value: ```ori type UserId = int; impl UserId: Formattable { @format (self, spec: FormatSpec) -> str = self.inner.format(spec: spec); } ``` ### 9.7.7 Error Codes | Code | Description | |------|-------------| | E0970 | Invalid format specification syntax | | E0971 | Format type not supported for this type | | E0972 | Type does not implement `Formattable` | ## 9.8 Default Trait The `Default` trait provides zero/empty values. ```ori trait Default { @default () -> Self; } ``` ### 9.8.1 Standard Implementations | Type | Default Value | |------|---------------| | `int` | `0` | | `float` | `0.0` | | `bool` | `false` | | `str` | `""` | | `byte` | `0` | | `char` | `'\0'` | | `void` | `()` | | `[T]` | `[]` | | `{K: V}` | `{}` | | `Set` | `Set.new()` | | `Option` | `None` | | `Duration` | `0ns` | | `Size` | `0b` | ### 9.8.2 Derivation `Default` is derivable for struct types when all fields implement `Default`: ```ori #derive(Default) type Config = { host: str, // "" port: int, // 0 debug: bool, // false } ``` Sum types cannot derive `Default` (ambiguous variant): ```ori #derive(Default) // error: cannot derive Default for sum type type Status = Pending | Running | Done; ``` ## 9.9 Traceable Trait The `Traceable` trait enables error trace propagation. ```ori trait Traceable { @with_trace (self, entry: TraceEntry) -> Self; @trace (self) -> str; @trace_entries (self) -> [TraceEntry]; @has_trace (self) -> bool; } ``` The `?` operator automatically adds trace entries at propagation points: ```ori @outer () -> Result = { let x = inner()?; // Adds trace entry for this location Ok(x * 2) } ``` ### 9.9.1 TraceEntry Type ```ori type TraceEntry = { function: str, // Function name with @ prefix file: str, // Source file path line: int, // Line number column: int, // Column number } ``` ### 9.9.2 Standard Implementations | Type | Implements | |------|------------| | `Error` | Yes | | `Result` where `E: Traceable` | Yes (delegates to E) | ## 9.10 Len Trait The `Len` trait provides length information for collections and sequences. ```ori trait Len { @len (self) -> int; } ``` ### 9.10.1 Semantic Requirements Implementations _shall_ satisfy: - **Non-negative**: `x.len() >= 0` for all `x` - **Deterministic**: `x.len()` returns the same value for unchanged `x` ### 9.10.2 String Length For `str`, `.len()` returns the **byte count**, not the codepoint or grapheme count: ```ori "hello".len() // 5 "café".len() // 5 (é is 2 bytes in UTF-8) "日本".len() // 6 (each character is 3 bytes) ``` ### 9.10.3 Standard Implementations | Type | Implements `Len` | Returns | |------|-------------------|---------| | `[T]` | Yes | Number of elements | | `str` | Yes | Number of bytes | | `{K: V}` | Yes | Number of entries | | `Set` | Yes | Number of elements | | `Range` | Yes | Number of values in range | | `(T₁, T₂, ...)` | Yes | Number of elements (statically known) | ### 9.10.4 Derivation `Len` cannot be derived. Types _shall_ implement it explicitly or be built-in. ### 9.10.5 Distinction from Iterator.count() The `Len` trait is distinct from `Iterator.count()`: | | `Len.len()` | `Iterator.count()` | |--|------------|-------------------| | **Complexity** | O(1) for built-in types | O(n) — consumes the iterator | | **Side effects** | None — non-consuming | Consuming — iterator is exhausted | | **Semantics** | Current size of collection | Number of remaining elements | Iterators do _not_ implement `Len`. To count iterator elements, use `.count()`. ## 9.11 IsEmpty Trait The `IsEmpty` trait provides emptiness checking for collections and sequences. ```ori trait IsEmpty { @is_empty (self) -> bool } ``` ### 9.11.1 Semantic Requirements Implementations _shall_ satisfy: - **Consistency**: `is_empty()` returns `true` if and only if the collection contains no elements - **Deterministic**: `x.is_empty()` returns the same value for unchanged `x` - **Relationship to Len**: If a type implements both `Len` and `IsEmpty`, then `x.is_empty() == (x.len() == 0)` _shall_ hold ### 9.11.2 Standard Implementations All built-in _Iterable_ types implement `IsEmpty`: | Type | Implements `IsEmpty` | Condition | |------|---------------------|-----------| | `[T]` | Yes | `true` if no elements | | `str` | Yes | `true` if zero bytes | | `{K: V}` | Yes | `true` if no entries | | `Set` | Yes | `true` if no elements | | `[T, max N]` | Yes | `true` if length is 0 | | `Range` | Yes | `true` if `start >= end` | ### 9.11.3 Types That Do Not Implement IsEmpty | Type | Reason | |------|--------| | Tuples `(T₁, T₂, ...)` | Fixed-size at compile time; never empty | | `Option` | Not a collection; use `is_none()` or pattern matching | | `Range` | Not iterable | ### 9.11.4 Relationship to Iterable `IsEmpty` is independent of `Iterable`. While all built-in `Iterable` types implement `IsEmpty`, a type _may_ implement `IsEmpty` without implementing `Iterable` (e.g., types where emptiness can be checked without iteration). ### 9.11.5 Derivation `IsEmpty` cannot be derived. Types _shall_ implement it explicitly or be built-in. ## 9.12 Comparable Trait The `Comparable` trait provides total ordering for values. ```ori trait Comparable: Eq { @compare (self, other: Self) -> Ordering; } ``` `Comparable` extends `Eq` — all comparable types shall also be equatable. ### 9.12.1 Mathematical Properties A valid `Comparable` implementation shall satisfy: **Reflexivity**: `a.compare(other: a) == Equal` **Antisymmetry**: If `a.compare(other: b) == Less`, then `b.compare(other: a) == Greater` **Transitivity**: If `a.compare(other: b) == Less` and `b.compare(other: c) == Less`, then `a.compare(other: c) == Less` **Consistency with Eq**: `a.compare(other: b) == Equal` if and only if `a == b` ### 9.12.2 Operator Derivation Types implementing `Comparable` automatically get comparison operators. The builtin `compare` function calls the trait method: ```ori compare(left: a, right: b) // → a.compare(other: b) ``` Operators desugar to `Ordering` method calls: ```ori a < b // a.compare(other: b).is_less() a <= b // a.compare(other: b).is_less_or_equal() a > b // a.compare(other: b).is_greater() a >= b // a.compare(other: b).is_greater_or_equal() ``` ### 9.12.3 Standard Implementations | Type | Ordering | |------|----------| | `int` | Numeric order | | `float` | IEEE 754 total order (NaN handling) | | `bool` | `false < true` | | `str` | Lexicographic (Unicode codepoint) | | `char` | Unicode codepoint | | `byte` | Numeric order | | `Duration` | Shorter < longer | | `Size` | Smaller < larger | | `[T]` where `T: Comparable` | Lexicographic | | `(T1, T2, ...)` where all `Ti: Comparable` | Lexicographic | | `Option` where `T: Comparable` | `None < Some(_)` | | `Result` where `T: Comparable, E: Comparable` | `Ok(_) < Err(_)`, then compare inner | | `Ordering` | `Less < Equal < Greater` | Maps and Sets are not Comparable (unordered collections). ### 9.12.4 Float Comparison Floats follow IEEE 754 total ordering: - `-Inf < negative < -0.0 < +0.0 < positive < +Inf` - `NaN` compares equal to itself and greater than all other values Note: For ordering purposes, `NaN == NaN`. This differs from `==` where `NaN != NaN`. ### 9.12.5 Derivation `Comparable` is derivable for user-defined types when all fields implement `Comparable`: ```ori #derive(Eq, Comparable) type Point = { x: int, y: int } // Generated: lexicographic comparison by field declaration order ``` For sum types, variants compare by declaration order (`Low < Medium < High`). ## 9.13 Hashable Trait The `Hashable` trait provides hash values for map keys and set elements. ```ori trait Hashable: Eq { @hash (self) -> int; } ``` `Hashable` extends `Eq` — all hashable types shall also be equatable. ### 9.13.1 Hash Invariant **Consistency with Eq**: If `a == b`, then `a.hash() == b.hash()` The converse is NOT required — different values may have the same hash (collisions are expected). ### 9.13.2 Standard Implementations | Type | Hash Method | |------|-------------| | `int` | Identity or bit-mixing | | `float` | Bit representation hash | | `bool` | `false` → 0, `true` → 1 | | `str` | FNV-1a or similar | | `char` | Codepoint value | | `byte` | Identity | | `Duration` | Hash of nanoseconds | | `Size` | Hash of bytes | | `[T]` where `T: Hashable` | Combined element hashes | | `{K: V}` where `K: Hashable, V: Hashable` | Combined entry hashes (order-independent) | | `Set` where `T: Hashable` | Combined element hashes (order-independent) | | `(T1, T2, ...)` where all `Ti: Hashable` | Combined element hashes | | `Option` where `T: Hashable` | `None` → 0, `Some(x)` → `x.hash()` with salt | | `Result` where `T: Hashable, E: Hashable` | Combined variant and value hash | ### 9.13.3 Float Hashing Floats hash consistently with equality: - `+0.0` and `-0.0` hash the same (they're equal) - `NaN` values hash consistently (all NaN equal for hashing) ### 9.13.4 Map Key and Set Element Requirements To use a type as a map key or set element, it shall implement both `Eq` and `Hashable`. Using a type that does not implement `Hashable` as a map key is an error (E2031): ```ori let map: {Point: str} = {}; // Point must be Eq + Hashable let set: Set = Set.new(); // Point must be Eq + Hashable ``` ### 9.13.5 hash_combine Function The `hash_combine` function in the prelude mixes hash values: ```ori @hash_combine (seed: int, value: int) -> int = seed ^ (value + 0x9e3779b9 + (seed << 6) + (seed >> 2)); ``` This follows the boost hash_combine pattern for good distribution. Users implementing custom `Hashable` can use this function directly. ### 9.13.6 Derivation `Hashable` is derivable for user-defined types when all fields implement `Hashable`: ```ori #derive(Eq, Hashable) type Point = { x: int, y: int } // Generated: combine field hashes using hash_combine ``` Deriving `Hashable` without `Eq` is an error (E2029). The hash invariant requires that equal values produce equal hashes, which cannot be guaranteed without an `Eq` implementation. ## 9.14 Into Trait The `Into` trait provides semantic, lossless type conversions. ```ori trait Into { @into (self) -> T; } ``` `Into` represents a conversion from `Self` to `T`. Unlike `as` conversions (which are built-in and handle representation changes), `Into` is user-extensible and represents semantic conversions between types. ### 9.14.1 Usage Conversions are always explicit. The caller shall invoke `.into()`: ```ori let error: Error = "something went wrong".into(); ``` When a function accepts `impl Into`, the caller shall still call `.into()` explicitly: ```ori @fail (err: impl Into) -> Never = panic(msg: err.into().message); fail(err: "simple message".into()); // Explicit .into() required fail(err: Error { message: "detailed" }); // No conversion needed ``` No implicit conversion occurs at call sites. This maintains Ori's "no implicit conversions" principle. ### 9.14.2 Standard Implementations | Source | Target | Notes | |--------|--------|-------| | `str` | `Error` | Creates Error with message | | `int` | `float` | Lossless numeric widening | | `Set` | `[T]` | Requires `T: Eq + Hashable` | NOTE `Into` is for lossless conversions only. Lossy conversions (like `float` to `int` truncation) require explicit `as` syntax. ### 9.14.3 Relationship to `as` | Mechanism | Fallible | Implicit | Extensible | Use Case | |-----------|----------|----------|------------|----------| | `as` | No | No | No | Primitive representation changes | | `as?` | Yes | No | No | Parsing, checked conversions | | `Into` | No | No | Yes | Semantic type conversions | ### 9.14.4 Custom Implementations User types may implement `Into` for meaningful conversions: ```ori type UserId = int; impl UserId: Into { @into (self) -> str = `user-{self.inner}`; } let id = UserId(42); let s: str = id.into(); // "user-42" ``` ### 9.14.5 No Blanket Identity There is no blanket `impl T: Into`. Each conversion shall be explicitly implemented. This ensures `impl Into` parameters remain meaningful — they indicate types that can be converted to `T`, not any type. ### 9.14.6 No Automatic Chaining Conversions do not chain automatically: ```ori // Given: A implements Into, B implements Into let a: A = ...; let c: C = a.into(); // ERROR: A does not implement Into let c: C = a.into().into(); // OK: explicit A → B → C ``` ### 9.14.7 Orphan Rules `Into` implementations follow standard orphan rules: - Implement in the module defining the source type, OR - Implement in the module defining the target type ### 9.14.8 Error Codes | Code | Description | |------|-------------| | E2036 | Type does not implement `Into` | | E2037 | Multiple `Into` implementations apply (ambiguous) | --- --- title: "Declarations" description: "Clause 10: Ori Language Specification — Declarations" order: 10 section: "Language" --- # 10 Declarations A _declaration_ binds an identifier to a program entity: a function, type, trait, implementation, constant, or module. > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS ## 10.0 General ### 10.0.1 Predeclared identifiers Certain identifiers are _predeclared_ in the universe scope (see [11.1](11-blocks-and-scope.md)). They are visible in all modules without explicit import and may be shadowed by user declarations. **Types:** `int`, `float`, `bool`, `str`, `byte`, `char`, `void`, `Never`, `Duration`, `Size` **Compound types:** `Option`, `Result`, `Error`, `Range`, `Set`, `Ordering` **Prelude types:** `TraceEntry`, `PanicInfo`, `FormatSpec`, `Alignment`, `Sign`, `FormatType`, `CancellationError`, `CancellationReason` **Traits:** `Eq`, `Comparable`, `Hashable`, `Printable`, `Formattable`, `Debug`, `Clone`, `Default`, `Drop`, `Len`, `IsEmpty`, `Iterator`, `DoubleEndedIterator`, `Iterable`, `Collect`, `Into`, `Traceable`, `Index`, `Sendable` **Operator traits:** `Add`, `Sub`, `Mul`, `Div`, `FloorDiv`, `Rem`, `Pow`, `MatMul`, `Neg`, `Not`, `BitNot`, `BitAnd`, `BitOr`, `BitXor`, `Shl`, `Shr`, `As`, `TryAs` **Constructors:** `Some`, `None`, `Ok`, `Err`, `Less`, `Equal`, `Greater` **Functions:** `print`, `panic`, `todo`, `unreachable`, `dbg`, `assert`, `assert_eq`, `assert_ne`, `assert_some`, `assert_none`, `assert_ok`, `assert_err`, `assert_panics`, `assert_panics_with`, `len`, `is_empty`, `is_some`, `is_none`, `is_ok`, `is_err`, `compare`, `min`, `max`, `hash_combine`, `repeat`, `is_cancelled`, `drop_early`, `compile_error`, `embed`, `has_embed` See [Annex C](annex-c-built-in-functions.md) for built-in function semantics. ### 10.0.2 Uniqueness of identifiers Within a scope, an identifier shall refer to at most one declaration. Two declarations in the same scope with the same identifier are a compile-time error, with the following exceptions: - **Function clauses**: Multiple declarations of the same function name in the same scope define multi-clause pattern matching (see 10.1.1). - **Impl blocks**: Multiple `impl` blocks for the same type are permitted. - **Trait implementations**: Implementations of different traits for the same type are permitted. A type and a function with the same name in the same scope is an error. Overloading by parameter types is not supported. NOTE Identifiers in inner scopes may _shadow_ identifiers from outer scopes. Shadowing does not create a conflict; it temporarily hides the outer binding. See [13.3.1](13-variables.md#1331-shadowing). ### 10.0.3 Exported identifiers An identifier prefixed with `pub` in its declaration is _exported_ and visible to importing modules. The `pub` keyword applies to: - Functions: `pub @name` - Types: `pub type Name` - Constants: `pub let $name` - Traits: `pub trait Name` - Default implementations: `pub def impl Trait` - Extensions: `pub extend Type` `pub` does not apply to local bindings, parameters, or loop variables. Visibility of `pub type` is type-level: exporting a type exports its name and constructors. Field access is restricted to the defining module. Trait method visibility is inherited from the trait's visibility. See [Clause 18](18-modules.md) for import and re-export rules. ### 10.0.4 Forward references Top-level declarations are visible throughout the entire module, regardless of textual order. A function may call another function defined later in the file. Mutual recursion between top-level functions is permitted. Local bindings are _not_ order-independent. A reference to a local binding before its `let` declaration is a compile-time error. EXAMPLE ```ori // Top-level: order-independent @is_even (n: int) -> bool = if n == 0 then true else is_odd(n - 1); @is_odd (n: int) -> bool = if n == 0 then false else is_even(n - 1); @f () -> int = { let $a = b; // error: 'b' is not yet declared let $b = 1; $a + $b } ``` Recursive types are permitted when the recursion passes through an indirection (such as `Option`, a list, or a sum variant). A struct field whose type is the struct itself (without indirection) is a compile-time error because it would require infinite size. ## 10.1 Functions ```ori @add (a: int, b: int) -> int = a + b; pub @identity (x: T) -> T = x; @sort (items: [T]) -> [T] = ...; @fetch (url: str) -> Result uses Http = Http.get(url); ``` - `@` prefix required - Return type required (`void` for no value) - Parameters are immutable (except `self` — see [13.5](13-variables.md#135-function-parameters)) - Private by default; `pub` exports - `uses` declares capability dependencies ### 10.1.1 Multiple Clauses A function may have multiple definitions (clauses) with patterns in parameter position: ```ori @factorial (0: int) -> int = 1; @factorial (n) -> int = n * factorial(n - 1); @fib (0: int) -> int = 0; @fib (1) -> int = 1; @fib (n) -> int = fib(n - 1) + fib(n - 2); ``` Clauses are matched top-to-bottom. All clauses shall have: - Same name - Same number of parameters - Same return type - Same capabilities The first clause establishes the function signature: - **Visibility**: `pub` only on first clause - **Generics**: Type parameters declared on first clause; in scope for all clauses - **Type annotations**: Required on first clause parameters; optional on subsequent clauses ```ori pub @len ([]: [T]) -> int = 0; @len ([_, ..tail]) -> int = 1 + len(tail); ``` Guards use `if` before `=`: ```ori @abs (n: int) -> int if n < 0 = -n; @abs (n) -> int = n; ``` All clauses together shall be exhaustive. The compiler warns about unreachable clauses. ### 10.1.2 Default Parameter Values Parameters may specify default values: ```ori @greet (name: str = "World") -> str = `Hello, {name}!`; @connect (host: str, port: int = 8080, timeout: Duration = 30s) -> Connection; ``` - Callers may omit parameters with defaults - Named arguments allow any defaulted parameter to be omitted, not just trailing ones - Default expressions are evaluated at call time, not definition time - Default expressions shall not reference other parameters ```ori greet() // "Hello, World!" greet(name: "Alice") // "Hello, Alice!" connect(host: "localhost") // uses default port and timeout connect(host: "localhost", timeout: 60s) // override timeout only ``` See [Expressions § Function Call](14-expressions.md#1413-function-call) for call semantics. ### 10.1.3 Variadic Parameters A _variadic parameter_ accepts zero or more arguments of the same type: ```ori @sum (numbers: ...int) -> int = numbers.fold(initial: 0, op: (acc, n) -> acc + n); sum(1, 2, 3) // 6 sum() // 0 (empty variadic) ``` Inside the function, the variadic parameter is received as a list. **Constraints:** | Rule | Description | |------|-------------| | One per function | At most one variadic parameter allowed | | Shall be last | Variadic parameter shall appear after all required parameters | | No default | Variadic parameters cannot have default values (default is empty list) | | Positional at call | Variadic arguments are always positional; parameter name cannot be used | ```ori @log (level: str, messages: ...str) -> void; log(level: "INFO", "Request", "User: 123") // Named + variadic @max (first: int, rest: ...int) -> int; // Requires at least one argument ``` **Allowed variadic types:** | Type | Example | Behavior | |------|---------|----------| | Concrete | `...int` | All arguments shall be `int` | | Generic | `...T` | Type inferred from arguments | | Trait object | `...Printable` | Arguments boxed as trait objects | ```ori @print_all (items: ...T) -> void; print_all(1, 2, 3) // T = int print_all(1, "a") // ERROR: cannot unify int and str @print_any (items: ...Printable) -> void; print_any(1, "hello", true) // OK: all implement Printable ``` **Spread into variadic:** The spread operator `...` expands a list into variadic arguments: ```ori let nums = [1, 2, 3]; sum(...nums) // 6 sum(0, ...nums, 10) // 14 ``` Spread in non-variadic function calls remains an error. **Type inference:** When a generic type parameter is only constrained by a variadic parameter, calls with zero arguments cannot infer the type: ```ori @collect (items: ...T) -> [T] = items; collect(1, 2, 3) // T = int inferred collect() // ERROR: cannot infer T collect() // OK: explicit type ``` **Function type representation:** A variadic function's type is represented as accepting a list: ```ori @sum (numbers: ...int) -> int = ...; let f: ([int]) -> int = sum; // Variadic stored as list-accepting function f([1, 2, 3]) // Must call with list when using function value sum(1, 2, 3) // Direct call retains variadic syntax ``` ### 10.1.4 C Variadic Functions C variadic functions use a different, untyped mechanism: ```ori extern "c" from "libc" { @printf (format: CPtr, ...) -> c_int as "printf"; } ``` | Feature | Ori `...T` | C `...` | |---------|------------|---------| | Type safety | Homogeneous, checked | Unchecked | | Context | Safe code | `unsafe { ... }` block only | | Type annotation | Required | None | C-style `...` (without type) is only valid in `extern "c"` declarations. Calling C variadic functions requires `unsafe`. See [FFI](26-ffi.md) for details on C interop. ## 10.2 Types ```ori type Point = { x: int, y: int } type Status = Pending | Running | Done | Failed(reason: str); type UserId = int; #derive(Eq, Clone) type User = { id: int, name: str } ``` ## 10.3 Traits ```ori trait Printable { @to_str (self) -> str; } trait Comparable: Eq { @compare (self, other: Self) -> Ordering; } trait Iterator { type Item; @next (self) -> Option; } ``` - `self` — instance - `Self` — implementing type ### 10.3.1 Default Type Parameters Type parameters on traits may have default values: ```ori trait Add { type Output; @add (self, rhs: Rhs) -> Self.Output; } ``` Semantics: 1. Default applies when impl omits the type argument 2. `Self` in default position refers to the implementing type at the impl site 3. Defaults are evaluated at impl site, not trait definition site 4. Parameters with defaults shall appear after all parameters without defaults ```ori impl Point: Add { // Rhs defaults to Self = Point @add (self, rhs: Point) -> Self = ...; } impl Vector2: Add { // Explicit Rhs = int @add (self, rhs: int) -> Self = ...; } ``` Later default parameters may reference earlier ones: ```ori trait Transform { @transform (self, input: Input) -> Output; } impl Parser: Transform { ... } // Input = Self = Parser, Output = Parser impl Parser: Transform { ... } // Input = str, Output = str impl Parser: Transform { ... } // Input = str, Output = Ast ``` ### 10.3.2 Default Associated Types Associated types in traits may have default values: ```ori trait Add { type Output = Self; // Defaults to implementing type @add (self, rhs: Rhs) -> Self.Output; } trait Container { type Item; type Iter = [Self.Item]; // Default references another associated type } ``` Semantics: 1. Default applies when impl omits the associated type 2. `Self` in default position refers to the implementing type at the impl site 3. Defaults may reference type parameters and other associated types 4. Defaults are evaluated at impl site, not trait definition site ```ori impl Point: Add { // Output defaults to Self = Point @add (self, rhs: Point) -> Self = ...; } impl Vector2: Add { type Output = Vector2; // Explicit override @add (self, rhs: int) -> Vector2 = ...; } ``` #### Bounds on Defaults Defaults shall satisfy any bounds on the associated type: ```ori trait Process { type Output: Clone = Self; // Default only valid if Self: Clone @process (self) -> Self.Output; } impl String: Process { // OK: String: Clone @process (self) -> Self = self.clone(); } impl Connection: Process { // ERROR if Connection: !Clone and no override // Must provide explicit Output type since Self doesn't satisfy Clone type Output = ConnectionHandle; @process (self) -> ConnectionHandle = ...; } ``` When an impl uses a default: 1. Substitute `Self` with the implementing type 2. Substitute any referenced associated types 3. Verify the resulting type satisfies all bounds If the default does not satisfy bounds after substitution, it is a compile error at the impl site. ### 10.3.3 Trait Associated Functions Traits may define associated functions (methods without `self`) that implementors shall provide: ```ori trait Default { @default () -> Self; } impl Point: Default { @default () -> Self = Point { x: 0, y: 0 }; } ``` Associated functions returning `Self` prevent the trait from being used as a trait object. See [Object Safety](08-types.md#882-object-safety) in Types. ## 10.4 Implementations ```ori impl Point { @new (x: int, y: int) -> Point = Point { x, y }; } impl Point: Printable { @to_str (self) -> str = "(" + str(self.x) + ", " + str(self.y) + ")"; } impl [T]: Printable { @to_str (self) -> str = ...; } ``` ### 10.4.1 Associated Functions An _associated function_ is a method defined in an `impl` block without a `self` parameter. Associated functions are called on the type itself, not on an instance. ```ori impl Point { // Associated function (no self) @origin () -> Point = Point { x: 0, y: 0 }; @new (x: int, y: int) -> Self = Point { x, y }; // Instance method (has self) @distance (self, other: Point) -> float = ...; } ``` Associated functions are called using `Type.method(args)`: ```ori let p = Point.origin(); let q = Point.new(x: 10, y: 20); ``` `Self` may be used as a return type in associated functions, referring to the implementing type. For generic types, full type arguments are required: ```ori let x: Option = Option.some(value: 42); ``` Extensions cannot define associated functions. Use inherent `impl` blocks for associated functions. ## 10.5 Default Implementations A _default implementation_ provides the standard behavior for a trait: ```ori pub def impl Http { @get (url: str) -> Result = ...; @post (url: str, body: str) -> Result = ...; } ``` When a module exports both a trait and its `def impl`, importing the trait automatically binds the default implementation. Default implementation methods do not have a `self` parameter — they are stateless. For configuration, use module-level bindings: ```ori let $timeout = 30s; pub def impl Http { @get (url: str) -> Result = __http_get(url: url, timeout: $timeout); } ``` Constraints: - One `def impl` per trait per module - Shall implement all trait methods - Method signatures shall match the trait - No `self` parameter ### 10.5.1 Import Conflicts A scope can have at most one `def impl` for each trait. Importing the same trait with defaults from two modules is a compile error: ```ori use "module_a" { Logger }; // Brings def impl use "module_b" { Logger }; // Error: conflicting default for Logger ``` To import a trait without its default: ```ori use "module_a" { Logger without def }; // Import trait, skip def impl ``` ### 10.5.2 Resolution Order When resolving a capability name: 1. Innermost `with...in` binding 2. Imported `def impl` 3. Module-local `def impl` Imported `def impl` takes precedence over module-local `def impl`. See [Capabilities](20-capabilities.md) for usage with capability traits. ## 10.6 Trait Resolution ### 10.6.1 Trait Inheritance (Diamond Problem) When a type inherits a trait through multiple paths, a single implementation satisfies all paths: ```ori trait A { @method (self) -> int; } trait B: A { } trait C: A { } trait D: B + C { } // D inherits A through both B and C impl MyType: D { @method (self) -> int = 42; // Single implementation satisfies A via B and C } ``` ### 10.6.2 Conflicting Default Implementations When multiple supertraits provide different default implementations for the same method, the implementing type shall provide an explicit implementation: ```ori trait A { @method (self) -> int = 0; } trait B: A { @method (self) -> int = 1; } trait C: A { @method (self) -> int = 2; } trait D: B + C { } impl MyType: D { } // ERROR: ambiguous default for @method impl MyType: D { @method (self) -> int = 3; // Explicit implementation resolves ambiguity } ``` ### 10.6.3 Coherence Rules _Coherence_ ensures that for any type `T` and trait `Trait`, there is at most one implementation of `Trait` for `T` visible in any compilation unit. An implementation `impl Type: Trait` is allowed only if at least one of these is true: 1. `Trait` is defined in the current module 2. `Type` is defined in the current module 3. `Type` is a generic parameter constrained in the current module ```ori // OK: Type is local type MyType = { ... } impl MyType: ExternalTrait { } // OK: Trait is local trait MyTrait { ... } impl ExternalType: MyTrait { } // ERROR: Both trait and type are external (orphan) impl std.Vec: std.Display { } // Error: orphan implementation ``` Blanket implementations (`impl T: Trait where ...`) follow the same rules. A duplicate implementation — where the same `Trait` and `Type` combination is implemented twice — is an error (E2010). When two blanket implementations with equal specificity could both apply, it is an error (E2021). ### 10.6.4 Method Resolution Order When calling `value.method()`: 1. **Inherent methods** — methods in `impl Type { }` (not trait impl) 2. **Trait methods from explicit bounds** — methods from `where T: Trait` 3. **Trait methods from in-scope traits** — traits imported into current scope 4. **Extension methods** — methods added via `extend` If multiple traits provide the same method and none are inherent, the call is ambiguous (E2023). Use fully-qualified syntax to disambiguate: ```ori A.method(x) // Calls A's implementation B.method(x) // Calls B's implementation ``` ### 10.6.5 Super Trait Method Calls An implementation can call the parent trait's default implementation using `Trait.method(self)`: ```ori trait Parent { @method (self) -> int = 10; } trait Child: Parent { @method (self) -> int = Parent.method(self) + 1; } impl MyType: Parent { @method (self) -> int = Parent.method(self) * 2; } ``` ### 10.6.6 Associated Type Disambiguation When a type implements multiple traits with same-named associated types, use qualified paths: ```ori trait A { type Item; } trait B { type Item; } // Qualified path syntax: Type::Trait::AssocType @f (c: C) where C::A::Item: Clone = ...; // To require both Items to be the same type: @g (c: C) where C::A::Item == C::B::Item = ...; ``` ### 10.6.7 Implementation Specificity When multiple implementations could apply, the most specific wins: 1. **Concrete** — `impl MyType: Trait` (most specific) 2. **Constrained blanket** — `impl T: Trait` 3. **Generic blanket** — `impl T: Trait` (least specific) It is an error if two applicable implementations have equal specificity (E2021). ### 10.6.8 Extension Method Conflicts Only one extension for a given method may be in scope. Conflicts are detected based on what is in scope, including re-exports: ```ori extension "a" { Iterator.sum } extension "b" { Iterator.sum } // ERROR: conflicting extension imports ``` ## 10.7 Attributes Attributes modify declarations with metadata or directives. > **Grammar:** See [grammar.ebnf](grammar.md) § ATTRIBUTES ### 10.7.1 Syntax ```ori #attribute_name #attribute_name(args) ``` Attributes precede the declaration they modify. Multiple attributes can be applied: ```ori #derive(Eq, Clone) #repr("c") type Point = { x: int, y: int } ``` ### 10.7.2 Standard Attributes #### #derive Generates trait implementations automatically: ```ori #derive(Eq, Hashable, Clone, Debug) type Point = { x: int, y: int } ``` See [Types § Derive](08-types.md#864-derive) for derivable traits and semantics. #### #repr Controls memory representation: | Syntax | Effect | |--------|--------| | `#repr("c")` | C-compatible struct layout | ```ori #repr("c") type CTimeSpec = { tv_sec: int, tv_nsec: int } ``` Required for structs passed to C via FFI. See [FFI § C Structs](26-ffi.md#2649-c-structs). #### #target Conditional compilation based on platform: ```ori #target(os: "linux") @linux_only () -> void = ...; #target(arch: "x86_64", os: "linux") @linux_x64 () -> void = ...; ``` See [Conditional Compilation](25-conditional-compilation.md) for full syntax. #### #cfg Conditional compilation based on build configuration: ```ori #cfg(debug) @debug_log (msg: str) -> void = print(msg: `[DEBUG] {msg}`); #cfg(feature: "ssl") @secure_connect () -> void = ...; ``` See [Conditional Compilation](25-conditional-compilation.md) for full syntax. ### 10.7.3 Test Attributes #### #skip Skips a test with an optional reason: ```ori #skip("pending implementation") @test_feature tests @feature () -> void = ...; ``` #### #compile_fail Asserts that code fails to compile with the expected error: ```ori #compile_fail("E0100") @test_type_error tests @f () -> void = let x: int = "string"; // Expected type error ``` #### #fail Asserts that a test panics with the expected message: ```ori #fail("index out of bounds") @test_panic tests @f () -> void = let list: [int] = []; list[0] // Expected panic ``` See [Testing](19-testing.md) for test semantics. ### 10.7.4 File-Level Attributes The `#!` prefix applies an attribute to the entire file: ```ori #!target(os: "linux") // Entire file is Linux-only ``` File-level attributes shall appear before any declarations. ## 10.8 Tests ```ori @test_add tests @add () -> void = { assert_eq(actual: add(a: 2, b: 3), expected: 5) } ``` See [Testing](19-testing.md). --- --- title: "Blocks and Scope" description: "Clause 11: Ori Language Specification — Blocks and Scope" order: 11 section: "Language" --- # 11 Blocks and Scope A _scope_ is a region of source code within which a name refers to a specific binding. ## 11.1 Scope Hierarchy Ori defines the following scope levels, from outermost to innermost: 1. **Universe scope** — Contains all predeclared identifiers: primitive types (`int`, `float`, `str`, `byte`, `char`, `bool`, `void`, `Never`), compound types (`Option`, `Result`, `Error`, `Ordering`, etc.), traits, built-in functions, and constructors (`Some`, `None`, `Ok`, `Err`, etc.). The universe scope encloses all modules. See [10.0.1](10-declarations.md#1001-predeclared-identifiers) for the complete list. 2. **Module scope** — Contains all top-level declarations within a module: functions, types, traits, implementations, constants, and imported identifiers. Module-scope declarations are visible throughout the module regardless of textual order (see [10.0.4](10-declarations.md#1004-forward-references)). Imports logically reside at module scope. 3. **Function scope** — Contains function parameters. Parameters are visible throughout the function body. 4. **Block scope** — Contains bindings introduced by `let` within a `{ }` block. A binding is visible from its declaration point to the end of the enclosing block. 5. **Pattern scope** — Contains bindings introduced by pattern matching in `match` arms, `for` loops, and destructuring patterns. These bindings are visible within the arm body or loop body. Name resolution searches from the innermost scope outward: block → function → module → imports → universe. The first matching binding is used. NOTE Capability bindings introduced by `with...in` are resolved at block scope level. ## 11.2 Scope-Creating Constructs The following constructs create scopes: | Construct | Scope contains | |-----------|----------------| | Function body | Parameters and body expression | | `{ }` block | All bindings within the block | | `for` loop | Loop variable and body | | `match` arm | Pattern bindings and arm expression | | `if` branches | Each branch expression | | `loop { }` | Body expression | | `with...in` | Capability binding and `in` expression | | Lambda | Parameters and body expression | ## 11.3 Lexical Scoping Ori uses _lexical scoping_. A name refers to the binding in the innermost enclosing scope that declares that name. ```ori { let x = 10; { let y = x + 5; // x visible from outer scope y } // y not visible here x } ``` Names are resolved at the point of use by searching outward through enclosing scopes. If no binding is found, the compiler reports an error. ## 11.4 Visibility A binding is visible from its declaration to the end of its enclosing scope. ```ori { // x not yet visible let x = 10; let y = x + 5; // x visible y } // x, y not visible ``` Bindings in a block are visible to all subsequent expressions in the block: ```ori { let a = 1; let b = a + 1; // a visible let c = b + 1; // a and b visible c } ``` ## 11.5 No Hoisting Bindings are not hoisted. A name cannot be used before its declaration: ```ori { let y = x + 1; // error: x not declared let x = 10; y } ``` ## 11.6 Shadowing A binding may _shadow_ an earlier binding with the same name. The new binding hides the previous one within its scope. ```ori { let x = 10; let x = x + 5; // shadows, x is now 15 x } ``` Shadowing applies to all bindings, including function parameters: ```ori @increment (x: int) -> int = { let x = x + 1; // shadows parameter x } ``` The shadowed binding becomes inaccessible; there is no way to refer to it. ## 11.7 Lambda Capture Lambdas capture variables from enclosing scopes by value. A _captured variable_ is a _free variable_ (referenced but not defined within the lambda) that exists in an enclosing scope. ```ori { let base = 10; let add_base = (x) -> x + base; // captures base = 10 add_base(5) // returns 15 } ``` ### 11.7.1 What Gets Captured A lambda captures all free variables referenced in its body: ```ori { let a = 1; let b = 2; let c = 3; let f = () -> a + b; // captures a and b, not c f() } ``` Variables not referenced are not captured. ### 11.7.2 Capture Timing Capture occurs at the moment of lambda creation, not at invocation: ```ori { let closures = []; for i in 0..3 do closures = closures + [() -> i]; // each captures current i closures[0](); // 0 closures[1](); // 1 closures[2]() // 2 } ``` ### 11.7.3 Capture Semantics Capture is a snapshot at lambda creation time. Reassigning the outer binding does not affect the captured value: ```ori { let x = 10; let f = () -> x * 2; // captures x = 10 x = 20; // reassigns x in outer scope f() // returns 20, not 40 } ``` ### 11.7.4 Immutability of Captured Bindings Lambdas cannot mutate captured bindings: ```ori { let x = 0; let inc = () -> x = x + 1; // error: cannot mutate captured binding inc() } ``` This restriction prevents side effects through closures and ensures ARC safety. A lambda may shadow a captured binding with a local one: ```ori { let x = 10; let f = () -> { let x = 20; // shadows captured x x }; f() // returns 20 } ``` ### 11.7.5 Escaping Closures An _escaping closure_ outlives the scope in which it was created: ```ori @make_adder (n: int) -> (int) -> int = x -> x + n; // escapes: returned from function ``` Because closures capture by value, escaping is always safe. The closure owns its captured data; no dangling references are possible. ### 11.7.6 Task Boundary Restrictions Closures passed to task-spawning patterns (`parallel`, `spawn`, `nursery`) shall capture only `Sendable` values. Captured values are moved into the task, making the original binding inaccessible. See [Concurrency Model § Capture and Ownership](22-concurrency-model.md#226-capture-and-ownership). ## 11.8 Nested Scopes Scopes may be nested to arbitrary depth. Inner scopes can access bindings from all enclosing scopes: ```ori { let a = 1; { let b = 2; { let c = a + b; // both visible c } } } ``` Each scope is independent; bindings in one branch do not affect another: ```ori if condition then { let x = 1; x } else { let x = 2; x } // different x, no conflict ``` ## 11.9 Label Scope Labels (`loop:name`, `for:name`) occupy a separate namespace from variable bindings. A label name does not conflict with a variable of the same name. A label is visible within the body of its labeled statement. Label names shall be unique within the enclosing function body; shadowing a label is a compile-time error. ```ori for:outer i in 0..10 do for:inner j in 0..10 do if i * j > 50 then break:outer; ``` See [Clause 16](16-control-flow.md) for `break` and `continue` with labels. ## 11.10 Type Parameter Scope Type parameters are visible in the following regions: | Declaration | Type parameter visible in | |-------------|---------------------------| | `@f(...)` | Parameter types, return type, `where` clause, body | | `type T = ...` | Field types, `where` clause | | `trait T { ... }` | Method signatures, associated types, `where` clause | | `impl ...` | Target type, trait name, method bodies, `where` clause | Type parameter names may shadow outer type parameters or type names, but this is discouraged. ## 11.11 Self and self The keyword `Self` (capitalized) refers to the implementing type. It is visible within: - `impl Type { ... }` — `Self` is `Type` - `trait Foo { ... }` — `Self` is the type that will implement the trait (abstract) `Self` is not visible in standalone functions, module scope, or `extend` blocks. The keyword `self` (lowercase) is a parameter name available in methods — functions declared with a `self` parameter. Unlike other parameters, `self` is a mutable binding: it may be reassigned and its fields may be mutated within the method body. Methods that mutate `self` implicitly propagate the modified value back to the caller through desugaring (see [mutable-self-proposal](../../proposals/approved/mutable-self-proposal.md)). `self` has type `Self`. ## 11.12 Block Expressions and Semicolons A _block expression_ has the form `{ statement* expression? }`. Each _statement_ within a block shall be terminated by `;`. The last expression in a block, if present and not terminated by `;`, is the _block value_ — the value produced by the block expression. A block in which every expression is terminated by `;` produces `void`. > **Grammar:** See [Annex A](grammar.ebnf) §block_expr, §statement, §block_ending_expression ### 11.12.1 Optional Semicolons After Block-Ending Expressions When an expression statement's last token is `}`, the trailing `;` is optional. This applies to the following expression forms when used as statements within a block: - `for...do { }` — for-do loops with block bodies - `while...do { }` — while-do loops with block bodies - `loop { }` — infinite loops - `if...then { }` — conditionals with block bodies (with or without `else`) - `match { }` — match expressions - `unsafe { }` — unsafe blocks - `block:label { }` — labeled blocks - `{ }` — bare block expressions The `;` remains required for expression statements that do not end with `}`. EXAMPLE ```ori @main () -> void = { for i in 0..10 do { print(msg: `{i}`) } // valid: last token is }, ; optional let $x = compute(); // required: last token is ), ; required if x > 0 then { handle_positive(x:) } else { handle_negative(x:) } // valid: last token is }, ; optional print(msg: `done`); // required: last token is ), ; required } ``` NOTE The optional-semicolon rule is purely syntactic — it does not change the semantics of the expression. An expression statement whose `;` is omitted is still treated as a statement (not a block value), unless it is the last expression in the block without `;`. --- --- title: "Constants" description: "Clause 12: Ori Language Specification — Constants" order: 12 section: "Language" --- # 12 Constants Constants are immutable bindings declared with the `$` prefix. > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (constant_decl), CONSTANT EXPRESSIONS ## 12.1 Immutable Bindings A binding prefixed with `$` is immutable — it cannot be reassigned after initialization. ```ori let $timeout = 30s; let $api_base = "https://api.example.com"; let $max_retries = 3; pub let $default_limit = 100; ``` The `$` prefix appears at definition, import, and usage sites: ```ori // Definition let $timeout = 30s; // Usage retry(op: fetch(url), attempts: $max_retries, timeout: $timeout); ``` ## 12.2 Module-Level Constants All module-level bindings shall be immutable. Mutable state is not permitted at module scope. ```ori let $timeout = 30s; // OK: immutable pub let $api_base = "https://..."; // OK: public, immutable let counter = 0; // error: module-level bindings must be immutable ``` Module-level constant initializers shall be _pure expressions_: they shall not use capabilities (`uses`), perform I/O, access mutable state, or reference non-constant bindings. This ensures deterministic initialization regardless of module load order. ```ori let $a = 5; // OK: literal let $b = $a * 2; // OK: constant expression let $c = $square(x: 10); // OK: const function call let $d = Clock.now(); // error: uses Clock capability let $e = read_file("cfg"); // error: uses FileSystem capability ``` The compiler evaluates constant expressions at compile time when possible. Pure expressions that cannot be fully evaluated at compile time (e.g., those calling non-const functions) produce runtime immutable bindings that are initialized once when the module is first loaded. ## 12.3 Local Immutable Bindings The `$` prefix may be used in local scope to create immutable local bindings: ```ori @process (input: int) -> int = { let $base = expensive_calculation(input); // ... $base cannot be reassigned ... $base * 2 } ``` ## 12.4 Identifier Rules The `$` prefix is a modifier on the identifier, not part of the name. A binding for `$x` and a binding for `x` refer to the same name — they cannot coexist in the same scope. ```ori let x = 5; let $x = 10; // error: 'x' is already defined in this scope ``` The `$` shall match between definition and usage: ```ori let $timeout = 30s; $timeout // OK timeout // error: undefined variable 'timeout' ``` ## 12.5 Const Functions A const function is a pure function bound to an immutable name. Const functions may be evaluated at compile time when all arguments are constant. ```ori let $square = (x: int) -> int = x * x; let $factorial = (n: int) -> int = if n <= 1 then 1 else n * $factorial(n: n - 1); // Evaluated at compile time let $fact_10 = $factorial(n: 10); // 3628800 ``` Const functions shall be pure: - No capabilities (`uses` clause) - No side effects - No mutable state access If called with non-constant arguments, the call is evaluated at runtime. ## 12.6 Constant Expressions A _constant expression_ is an expression whose value can be determined at compile time. The following are constant expressions: - Literals: integer, float, string, character, boolean, duration, size - References to `$`-prefixed bindings whose initializers are constant expressions - Arithmetic, comparison, logical, and string concatenation operations where all operands are constant - Calls to const functions (`$`-prefixed) where all arguments are constant ```ori 42 // constant 1 + 2 * 3 // constant "hello" + " world" // constant true && false // constant $square(x: $a) // constant if $square is const and $a is constant ``` The following are _not_ constant expressions: - References to mutable bindings - Calls to non-const functions - Expressions using capabilities - Expressions containing runtime values (function parameters, loop variables) Arithmetic overflow in a constant expression is a compile-time error, not a runtime panic. See [Clause 24](24-constant-expressions.md) for evaluation limits and detailed rules. ## 12.7 Imports When importing immutable bindings, the `$` shall be included: ```ori // config.ori pub let $timeout = 30s; // client.ori use "./config" { $timeout }; // OK use "./config" { timeout }; // error: 'timeout' not found ``` ## 12.8 Constraints - Module-level bindings shall use `$` prefix (immutable required) - `$`-prefixed bindings cannot be reassigned - `$` and non-`$` bindings with the same name cannot coexist in the same scope --- --- title: "Variables" description: "Clause 13: Ori Language Specification — Variables" order: 13 section: "Language" --- # 13 Variables A _variable_ is a storage location identified by a name. Every variable has a type and holds a value of that type. > **Grammar:** See [grammar.ebnf](grammar.md) § EXPRESSIONS (let_expr, assignment, binding_pattern) ## 13.1 Bindings A `let` binding introduces an identifier into the current scope. Every `let` binding shall have an initializer expression. Uninitialized variables are not permitted. ```ori let x = 42; // mutable let name: str = "Alice"; // mutable, with type annotation let $timeout = 30s; // immutable ($ prefix) let y: int; // error: binding requires initializer ``` Bindings are mutable by default. The `$` prefix marks a binding as immutable (see [Clause 12](12-constants.md)). Type annotations are optional; types are inferred when omitted. When a type annotation is present, the annotated type shall match the inferred type. ## 13.2 Mutability Bindings without `$` prefix are mutable: ```ori let x = 0; x = x + 1; // OK: mutable binding let $y = 10; $y = 20; // error: cannot assign to immutable binding '$y' ``` The `$` prefix marks a binding as immutable. See [Constants](12-constants.md) for details. ### 13.2.1 Cannot Reassign - Immutable bindings (`$`-prefixed) - Function parameters (except `self` — see 13.5) - Loop variables ```ori @add (a: int, b: int) -> int = { a = 10; // error: cannot assign to parameter a + b } for item in items do item = other // error: cannot assign to loop variable ``` ## 13.3 Scope Bindings are visible from declaration to end of enclosing block. ```ori { let x = 10; let y = x + 5; // x visible y } // x, y not visible ``` ### 13.3.1 Shadowing Bindings may shadow earlier bindings with the same name. Shadowing can change mutability: ```ori { let x = 10; // mutable let $x = x + 5; // immutable, shadows outer x $x // 15 } { let $x = 10; // immutable { let x = $x * 2; // mutable, shadows outer $x x = x + 1; // OK: inner x is mutable x } } ``` The `$` prefix shall match between definition and usage within the same binding scope. ## 13.4 Destructuring Patterns destructure composite values. The `$` prefix applies to individual bindings: ```ori let { x, y } = point; // both mutable let { $x, y } = point; // x immutable, y mutable let { x: px, y: py } = point; // rename, both mutable let (a, b) = pair; // both mutable let ($a, $b) = pair; // both immutable let [head, ..tail] = list; // head mutable, tail mutable let [$head, ..tail] = list; // head immutable, tail mutable let { position: { x, y } } = entity; // nested destructure ``` Pattern shall match value structure. ## 13.5 Function Parameters Parameters are immutable bindings scoped to the function body: ```ori @add (a: int, b: int) -> int = a + b; ``` Parameters cannot be reassigned regardless of `$` prefix, with one exception: `self` is a mutable binding in method bodies. `self` may be reassigned and its fields may be mutated. When a method mutates `self`, the modified value is implicitly propagated back to the caller through desugaring. See [11.11](11-blocks-and-scope.md#1111-self-and-self). ## 13.6 Assignment Semantics Assignment is _value copy_. Ori uses value semantics: after `x = y`, `x` and `y` hold independent values. No aliasing exists between them. ```ori let a = [1, 2, 3]; let b = a; // b is an independent copy b[0] = 99; // does not affect a ``` NOTE The compiler may use copy-on-write (COW) or reference counting (ARC) internally to defer physical copying until mutation occurs. This is an optimization that does not affect observable semantics. Assignment to an immutable binding (`$`-prefixed) is a compile-time error. Assignment to a function parameter or loop variable is a compile-time error. See 13.2.1. ### 13.6.1 Field assignment Field assignment `x.field = v` desugars to `x = { ...x, field: v }`. The binding `x` shall be mutable. ### 13.6.2 Index assignment Index assignment `x[i] = v` desugars to `x = x.updated(key: i, value: v)`. The binding `x` shall be mutable. ### 13.6.3 Compound assignment Compound assignment `x op= y` desugars to `x = x op y`. See [7.4.3](07-lexical-elements.md#743-compound-assignment-operators). ## 13.7 Drop Ordering A value is _dropped_ (its `Drop` implementation is called, if any) when its binding goes out of scope. Within a block, values are dropped in reverse declaration order: ```ori { let a = acquire_a(); // dropped third let b = acquire_b(); // dropped second let c = acquire_c(); // dropped first result } ``` Drop order for function parameters after the body returns is implementation-defined. The built-in function `drop_early(value:)` explicitly drops a value before the end of its scope. After `drop_early(value: x)`, the binding `x` is inaccessible; any subsequent use is a compile-time error. `drop_early` works on both mutable and immutable bindings — it concerns ownership, not mutability. See [Clause 21](21-memory-model.md) for details on the ARC memory model. ## 13.8 Blank Identifier The underscore `_` used alone in a pattern is the _blank identifier_. It matches any value and does not create a binding. ```ori let _ = compute(); // evaluates compute(), discards result match x { _ -> 0 } // matches anything let (_, b) = pair; // discards first element ``` In `let _ = expr`, the expression is fully evaluated (including side effects and any `Drop` implementation of the result), but no binding is created. Multiple `_` patterns may appear in the same pattern, unlike named bindings which shall be unique. See [Clause 15](15-patterns.md) for pattern matching rules. --- --- title: "Expressions" description: "Clause 14: Ori Language Specification — Expressions" order: 14 section: "Language" --- # 14 Expressions Expressions compute values. > **Grammar:** See [grammar.ebnf](grammar.md) § EXPRESSIONS ## 14.1 Postfix Expressions ### 14.1.1 Field and Method Access > **Grammar:** See [grammar.ebnf](grammar.md) § `postfix_op`, `member_name` ```ori point.x; list.len(); ``` The member name after `.` may be an identifier or a reserved keyword. Keywords are valid in member position because the `.` prefix provides unambiguous context: ```ori ordering.then(other: Less); // method call — `then` keyword allowed after `.` point.type; // field access — `type` keyword allowed after `.` ``` Integer literals are valid in member position for tuple field access. The index is zero-based and shall be within the tuple's arity: ```ori let pair = (10, "hello"); pair.0; // 10 pair.1; // "hello" ``` An out-of-bounds index is a compile-time error. Tuple field access is equivalent to destructuring but provides direct positional access without binding all elements. Chained tuple field access on nested tuples shall use parentheses because the lexer tokenizes `0.1` as a float literal: ```ori let nested = ((1, 2), (3, 4)); (nested.0).1; // 2 — parentheses required nested.0.1; // error: lexer sees 0.1 as float ``` ### 14.1.2 Index Access ```ori list[0]; list[# - 1]; // # is length within brackets map["key"]; // returns Option ``` Lists/strings panic on out-of-bounds; maps return `Option`. #### Index Trait User-defined types can implement the `Index` trait for custom subscripting: ```ori trait Index { @index (self, key: Key) -> Value; } ``` The compiler desugars subscript expressions to trait method calls: ```ori x[key]; // Desugars to: x.index(key: key); ``` A type may implement `Index` for multiple key types: ```ori impl JsonValue: Index> { ... } impl JsonValue: Index> { ... } ``` If the key type is ambiguous, the call is a compile-time error. Return types encode error handling strategy: - `T` — panics on invalid key (fixed-size containers) - `Option` — returns `None` for missing keys (sparse data) - `Result` — returns detailed errors (external data) Built-in implementations: - `[T]` implements `Index` (panics on out-of-bounds) - `[T, max N]` implements `Index` (same as `[T]`) - `{K: V}` implements `Index>` - `str` implements `Index` (single codepoint, panics on out-of-bounds) The `#` length shorthand is supported only for built-in types. Custom types use `len()` explicitly. ### 14.1.3 Function Call ```ori add(a: 1, b: 2); fetch_user(id: 1); print(msg: "hello"); assert_eq(actual: result, expected: 10); ``` Named arguments are required for direct function and method calls. Argument names shall match parameter names. Argument order is irrelevant. Positional arguments are permitted in three cases: 1. Type conversion functions (`int`, `float`, `str`, `byte`): ```ori int(3.14); // OK: type conversion float(42); // OK: type conversion str(value); // OK: type conversion ``` 2. Calls through function variables (parameter names are unknowable): ```ori let f = (x: int) -> x + 1; f(5); // OK: calling through variable let apply = (fn: (int) -> int, val: int) -> fn(val); apply(fn: inc, val: 10); // outer call: named required // inner fn(val): positional OK ``` 3. Single-parameter functions called with inline lambda expressions: ```ori items.map(x -> x * 2); // OK: lambda literal items.filter(x -> x > 0); // OK: lambda literal items.map(transform: x -> x * 2); // OK: named always works let double = x -> x * 2; items.map(double); // error: named arg required items.map(transform: double); // OK: function reference needs name ``` A lambda expression is `x -> expr`, `(a, b) -> expr`, `() -> expr`, or `(x: Type) -> Type = expr`. Function references and variables holding functions are not lambda expressions and require named arguments. For methods, `self` is not counted when determining "single parameter." A method like `map(transform: fn)` has one explicit parameter, so lambda arguments may be positional. It is a compile-time error to use positional arguments in direct function or method calls outside these three cases. ### 14.1.4 Error Propagation ```ori value?; // returns Err early if Err ``` ### 14.1.5 Conversion Expressions The `as` and `as?` operators convert values between types. ```ori 42 as float; // 42.0 (infallible) "42" as? int; // Some(42) (fallible) ``` **Syntax:** | Form | Semantics | Return Type | |------|-----------|-------------| | `expr as Type` | Infallible conversion | `Type` | | `expr as? Type` | Fallible conversion | `Option` | **Backing Traits:** - `expr as Type` desugars to `As.as(self: expr)` - `expr as? Type` desugars to `TryAs.try_as(self: expr)` See [Types § Conversion Traits](08-types.md#811-conversion-traits) for trait definitions. **Compile-Time Enforcement:** The compiler enforces that `as` is only used for conversions that cannot fail: ```ori 42 as float; // OK: int -> float always succeeds "42" as int; // ERROR: str -> int can fail, use `as?` 3.14 as int; // ERROR: lossy conversion, use explicit method ``` Lossy conversions (like `float -> int`) require explicit methods: ```ori 3.99.truncate(); // 3 (toward zero) 3.99.round(); // 4 (nearest) 3.99.floor(); // 3 (toward negative infinity) 3.99.ceil(); // 4 (toward positive infinity) ``` **Built-in `as` conversions:** The following table lists all built-in infallible conversions. Conversions not in this table are compile-time errors for `as`. User types implement the `As` trait. | Source | Target | Behavior | |--------|--------|----------| | `int` | `float` | Exact representation (within i64 range) | | `int` | `byte` | Panic if value outside 0–255 | | `int` | `char` | Panic if not a valid Unicode scalar value | | `byte` | `int` | Zero-extend (always succeeds) | | `byte` | `char` | Latin-1 interpretation U+0000–U+00FF (always succeeds) | | `char` | `int` | Unicode code point value (always succeeds) | | `char` | `str` | Single-character string (always succeeds) | NOTE `float` to `int` is not an `as` conversion because it is lossy. Use `.truncate()`, `.round()`, `.floor()`, or `.ceil()` for explicit rounding. **Built-in `as?` conversions:** The following table lists all built-in fallible conversions. Conversions not in this table are compile-time errors for `as?`. User types implement the `TryAs` trait. | Source | Target | Returns | |--------|--------|---------| | `str` | `int` | `Some(n)` if valid integer, `None` otherwise | | `str` | `float` | `Some(f)` if valid float, `None` otherwise | | `str` | `bool` | `Some(b)` for `"true"`/`"false"` (case-sensitive), `None` otherwise | | `int` | `byte` | `Some(b)` if 0–255, `None` otherwise | | `int` | `char` | `Some(c)` if valid Unicode scalar value, `None` otherwise | | `float` | `int` | `Some(n)` if whole number, no precision loss, and within i64 range; `None` otherwise | Parsing rules for `str` → `int`: leading and trailing whitespace is stripped; leading `+` or `-` is accepted; digit separators (`_`), hex (`0x`), and binary (`0b`) prefixes are rejected; overflow produces `None`. Parsing rules for `str` → `float`: leading and trailing whitespace is stripped; the strings `"inf"`, `"-inf"`, and `"nan"` (case-insensitive) are accepted; scientific notation (`1.5e10`) is accepted. **Chaining:** `as` and `as?` are postfix operators that chain naturally: ```ori input.trim() as? int; // (input.trim()) as? int items[0] as str; // (items[0]) as str get_value()? as float; // (get_value()?) as float ``` ### 14.1.6 Type narrowing Ori does not perform implicit type narrowing after conditional checks. The type of a variable does not change based on control flow. ```ori let x: Option = Some(42); if is_some(x) then // x is still Option here, NOT int match x { Some(v) -> v, None -> 0 } else 0 ``` To extract a value, use `match` for destructuring. This is the idiomatic Ori pattern for working with `Option` and `Result`. NOTE Type narrowing _does_ occur within `match` arms: a pattern `Some(v)` binds `v` with type `int`, not `Option`. ## 14.2 Unary Expressions ### 14.2.1 Logical Not (`!`) Inverts a boolean value. ```ori !true; // false !false; // true !!x; // x (double negation) ``` Type constraint: `! : bool -> bool`. It is a compile-time error to apply `!` to non-boolean types. For bitwise complement of integers, use `~`. ### 14.2.2 Arithmetic Negation (`-`) Negates a numeric value. ```ori -42; // -42 -3.14; // -3.14 -(-5); // 5 ``` Type constraints: - `- : int -> int` - `- : float -> float` - `- : Duration -> Duration` Integer negation panics on overflow: `-int.min` panics because the positive result does not fit in `int`. Float negation never overflows (flips sign bit). Duration negation follows the same overflow rules as integer negation. It is a compile-time error to apply unary `-` to `Size` (byte counts are non-negative). ### 14.2.3 Bitwise Not (`~`) Inverts all bits of an integer. ```ori ~0; // -1 (all bits set) ~(-1); // 0 ~5; // -6 ``` Type constraints: - `~ : int -> int` - `~ : byte -> byte` For `int`, `~x` is equivalent to `-(x + 1)`. For `byte`, the result is the bitwise complement within 8 bits. It is a compile-time error to apply `~` to `bool`. Use `!` for boolean negation. ## 14.3 Binary Expressions | Operator | Operation | |----------|-----------| | `+` `-` `*` `/` | Arithmetic | | `%` | Modulo | | `div` | Floor division | | `==` `!=` `<` `>` `<=` `>=` | Comparison | | `&&` `\|\|` | Logical (short-circuit) | | `&` `\|` `^` `~` | Bitwise | | `<<` `>>` | Shift | | `..` `..=` | Range | | `by` | Range step | | `??` | Coalesce (None/Err → default) | ### 14.3.1 Operator Type Constraints Binary operators require operands of matching types. No implicit conversions. **Arithmetic** (`+` `-` `*` `/`): | Left | Right | Result | |------|-------|--------| | `int` | `int` | `int` | | `float` | `float` | `float` | **String concatenation** (`+`): | Left | Right | Result | |------|-------|--------| | `str` | `str` | `str` | **Integer-only** (`%` `div`): | Left | Right | Result | |------|-------|--------| | `int` | `int` | `int` | **Bitwise** (`&` `|` `^`): | Left | Right | Result | |------|-------|--------| | `int` | `int` | `int` | | `byte` | `byte` | `byte` | **Shift** (`<<` `>>`): | Left | Right | Result | |------|-------|--------| | `int` | `int` | `int` | | `byte` | `int` | `byte` | The shift count is always `int`. It is a compile-time error to mix `int` and `byte` for bitwise AND/OR/XOR. **Comparison** (`<` `>` `<=` `>=`): Operands shall be the same type implementing `Comparable`. Returns `bool`. **Equality** (`==` `!=`): Operands shall be the same type implementing `Eq`. Returns `bool`. Mixed-type operations are compile errors: ```ori 1 + 2.0; // error: mismatched types int and float float(1) + 2.0; // OK: 3.0 1 + int(2.0); // OK: 3 ``` ### 14.3.2 Numeric Behavior **Integer overflow**: Panics. Addition, subtraction, multiplication, and negation all panic on overflow. ```ori let max: int = 9223372036854775807; max + 1; // panic: integer overflow int.min - 1; // panic: integer overflow -int.min; // panic: integer overflow (negation) ``` Programs requiring wrapping or saturating arithmetic should use functions from `std.math`. **Shift overflow**: Shift operations panic when the shift count is negative, exceeds the bit width, or the result overflows. ```ori 1 << 63; // panic: shift overflow (result doesn't fit in signed int) 1 << 64; // panic: shift count exceeds bit width 1 << -1; // panic: negative shift count 16 >> 64; // panic: shift count exceeds bit width ``` For `int` (signed, range -2⁶³ to 2⁶³ - 1), valid shift counts are 0 to 62 for left shift when the result shall remain representable. For right shift, counts 0 to 63 are valid. For `byte` (unsigned, range 0 to 255), valid shift counts are 0 to 7. **Integer division and modulo overflow**: The expression `int.min / -1` and `int.min % -1` panic because the mathematical result cannot be represented. ```ori int.min div -1; // panic: integer overflow int.min % -1; // panic: integer overflow ``` **Integer division by zero**: Panics. ```ori 5 / 0; // panic: division by zero 5 % 0; // panic: modulo by zero ``` **Float division by zero**: Returns infinity or NaN per IEEE 754. ```ori 1.0 / 0.0; // Inf -1.0 / 0.0; // -Inf 0.0 / 0.0; // NaN ``` **Float NaN propagation**: Any operation involving NaN produces NaN. ```ori NaN + 1.0; // NaN NaN == NaN; // false (IEEE 754) NaN != NaN; // true ``` **Float comparison**: Exact bit comparison. No epsilon tolerance. ```ori 0.1 + 0.2 == 0.3; // false (floating-point representation) ``` ## 14.4 Operator Precedence Operators are listed from highest to lowest precedence: | Level | Operators | Associativity | Description | |-------|-----------|---------------|-------------| | 1 | `.` `[]` `()` `?` `as` `as?` | Left | Postfix | | 2 | `**` | Right | Power | | 3 | `!` `-` `~` | Right | Unary | | 4 | `*` `/` `%` `div` `@` | Left | Multiplicative | | 5 | `+` `-` | Left | Additive | | 6 | `<<` `>>` | Left | Shift | | 7 | `..` `..=` `by` | Left | Range | | 8 | `<` `>` `<=` `>=` | Left | Relational | | 9 | `==` `!=` | Left | Equality | | 10 | `&` | Left | Bitwise AND | | 11 | `^` | Left | Bitwise XOR | | 12 | `\|` | Left | Bitwise OR | | 13 | `&&` | Left | Logical AND | | 14 | `\|\|` | Left | Logical OR | | 15 | `??` | Right | Coalesce | | 16 | `\|>` | Left | Pipe | Parentheses override precedence: ```ori (a & b) == 0; // Compare result of AND with 0 a & b == 0; // Parsed as a & (b == 0) — likely not intended ``` ## 14.5 Operator Traits Operators are desugared to trait method calls. User-defined types can implement operator traits to support operator syntax. ### 14.5.1 Arithmetic Operators | Operator | Trait | Method | |----------|-------|--------| | `a + b` | `Add` | `a.add(rhs: b)` | | `a - b` | `Sub` | `a.subtract(rhs: b)` | | `a * b` | `Mul` | `a.multiply(rhs: b)` | | `a / b` | `Div` | `a.divide(rhs: b)` | | `a div b` | `FloorDiv` | `a.floor_divide(rhs: b)` | | `a % b` | `Rem` | `a.remainder(rhs: b)` | | `a ** b` | `Pow` | `a.power(rhs: b)` | | `a @ b` | `MatMul` | `a.matrix_multiply(rhs: b)` | ### 14.5.2 Unary Operators | Operator | Trait | Method | |----------|-------|--------| | `-a` | `Neg` | `a.negate()` | | `!a` | `Not` | `a.not()` | | `~a` | `BitNot` | `a.bit_not()` | ### 14.5.3 Bitwise Operators | Operator | Trait | Method | |----------|-------|--------| | `a & b` | `BitAnd` | `a.bit_and(rhs: b)` | | `a \| b` | `BitOr` | `a.bit_or(rhs: b)` | | `a ^ b` | `BitXor` | `a.bit_xor(rhs: b)` | | `a << b` | `Shl` | `a.shift_left(rhs: b)` | | `a >> b` | `Shr` | `a.shift_right(rhs: b)` | ### 14.5.4 Comparison Operators | Operator | Trait | Method | |----------|-------|--------| | `a == b` | `Eq` | `a.equals(other: b)` | | `a != b` | `Eq` | `!a.equals(other: b)` | | `a < b` | `Comparable` | `a.compare(other: b).is_less()` | | `a <= b` | `Comparable` | `a.compare(other: b).is_less_or_equal()` | | `a > b` | `Comparable` | `a.compare(other: b).is_greater()` | | `a >= b` | `Comparable` | `a.compare(other: b).is_greater_or_equal()` | ### 14.5.5 Trait Definitions Operator traits use default type parameters and default associated types: ```ori trait Add { type Output = Self; @add (self, rhs: Rhs) -> Self.Output; } ``` The `Rhs` parameter defaults to `Self`, and `Output` defaults to `Self`. Implementations may override either. NOTE The `Div` trait method is named `divide` rather than `div` because `div` is a reserved keyword for the floor division operator. ### 14.5.6 User-Defined Example ```ori type Vector2 = { x: float, y: float } impl Vector2: Add { @add (self, rhs: Vector2) -> Self = Vector2 { x: self.x + rhs.x, y: self.y + rhs.y, } } let a = Vector2 { x: 1.0, y: 2.0 }; let b = Vector2 { x: 3.0, y: 4.0 }; let sum = a + b; // Vector2 { x: 4.0, y: 6.0 } ``` ### 14.5.7 Mixed-Type Operations Traits support different operand types. Commutative operations require both orderings: ```ori // Duration * int impl Duration: Mul { type Output = Duration; @multiply (self, n: int) -> Duration = ...; } // int * Duration impl int: Mul { type Output = Duration; @multiply (self, d: Duration) -> Duration = d * self; } ``` The compiler does not automatically commute operands. ### 14.5.8 Built-in Implementations Primitive types have built-in implementations for their applicable operators. These implementations use compiler intrinsics. See [Declarations § Traits](10-declarations.md#103-traits) for trait definition syntax. ## 14.6 Range Expressions Range expressions produce `Range` values. ```ori 0..10; // 0, 1, 2, ..., 9 (exclusive) 0..=10; // 0, 1, 2, ..., 10 (inclusive) ``` ### 14.6.1 Range with Step The `by` keyword specifies a step value for non-unit increments: ```ori 0..10 by 2; // 0, 2, 4, 6, 8 0..=10 by 2; // 0, 2, 4, 6, 8, 10 10..0 by -1; // 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 10..=0 by -2; // 10, 8, 6, 4, 2, 0 ``` `by` is a context-sensitive keyword recognized only following a range expression. **Type constraints:** - Range with step is supported only for `int` ranges - Start, end, and step shall all be `int` - It is a compile-time error to use `by` with non-integer ranges **Runtime behavior:** - Step of zero causes a panic - Mismatched direction produces an empty range (no panic) ```ori 0..10 by 0; // panic: step cannot be zero 0..10 by -1; // empty range (can't go from 0 to 10 with negative step) 10..0 by 1; // empty range (can't go from 10 to 0 with positive step) ``` ### 14.6.2 Infinite Ranges Omitting the end creates an unbounded ascending range: ```ori 0..; // 0, 1, 2, 3, ... (infinite ascending) 100..; // 100, 101, 102, ... (infinite ascending from 100) 0.. by 2; // 0, 2, 4, 6, ... (infinite ascending by 2) 0.. by -1; // 0, -1, -2, ... (infinite descending) ``` **Type constraints:** - Infinite ranges are supported only for `int` - The step shall be non-zero (zero step panics) **Semantics:** - `start..` creates an unbounded range with step +1 - `start.. by step` creates an unbounded range with explicit step - Infinite ranges implement `Iterable` but NOT `DoubleEndedIterator` (no end to iterate from) Infinite ranges shall be bounded before terminal operations like `collect()`: ```ori (0..).iter().take(count: 10).collect(); // OK: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (0..).iter().collect(); // infinite loop, eventually OOM ``` Implementations SHOULD warn on obvious unbounded consumption patterns. ## 14.7 With Expression ```ori with Http = MockHttp { ... } in fetch("/data"); ``` ## 14.8 Let Binding ```ori let x = 5; // mutable let $x = 5; // immutable let { x, y } = point; let { $x, y } = point; // x immutable, y mutable ``` ## 14.9 Conditional > **Grammar:** See [grammar.ebnf](grammar.md) § if_expr ```ori if x > 0 then "positive" else "non-positive"; ``` The _condition_ shall have type `bool`. It is a compile-time error if the condition has any other type. ### 14.9.1 Branch Evaluation Only one branch is evaluated at runtime. The unevaluated branch does not execute. This is guaranteed and observable (side effects in the unevaluated branch do not occur). ### 14.9.2 Type Unification When `else` is present, both branches shall produce types that unify to a common type: ```ori if cond then 1 else 2; // type: int if cond then Some(1) else None; // type: Option if cond then 1 else "two"; // error: cannot unify int and str ``` ### 14.9.3 Without Else When `else` is omitted, the expression has type `void`. The `then` branch shall have type `void` or `Never`: ```ori // Valid: then-branch is void if debug then print(msg: "debug mode"); // Valid: then-branch is Never (coerces to void) if !valid then panic(msg: "invalid state"); // Invalid: then-branch has non-void type without else if x > 0 then "positive"; // error: non-void then-branch requires else ``` When the `then` branch has type `Never`, it coerces to `void`. ### 14.9.4 Never Type Coercion The `Never` type coerces to any type in conditional branches: ```ori let x: int = if condition then 42 else panic(msg: "unreachable"); // else branch is Never, coerces to int ``` If both branches have type `Never`, the expression has type `Never`: ```ori let x = if a then panic(msg: "a") else panic(msg: "b"); // type: Never ``` ### 14.9.5 Else-If Chains ```ori if condition1 then expression1 else if condition2 then expression2 else expression3 ``` The grammar treats `else if` as a single production for parsing convenience, but semantically the `else` branch contains another `if` expression. ### 14.9.6 Struct Literal Restriction Struct literals are not permitted directly in the condition position. This prevents parsing ambiguity with block expressions: ```ori if Point { x: 0, y: 0 } then ...; // error: struct literal in condition if (Point { x: 0, y: 0 }) then ...; // OK: parentheses re-enable struct literals ``` The parser disables struct literal parsing in the condition context. Parenthesized expressions re-enable it. ## 14.10 For Expression > **Grammar:** See [grammar.ebnf](grammar.md) § For Expression ### 14.10.1 For-Do The `for...do` expression iterates for side effects and returns `void`: ```ori for item in items do print(msg: item); for (key, value) in map do process(key: key, value: value); ``` The source shall implement `Iterable`. The binding supports destructuring patterns. ### 14.10.2 Guard Condition An optional `if` clause filters elements: ```ori for x in items if x > 0 do process(x: x); ``` The guard is evaluated per item before the body. ### 14.10.3 Break and Continue In `for...do`, `break` exits the loop and `continue` skips to the next iteration: ```ori for x in items do { if done(x) then break; if skip(x) then continue; process(x: x); } ``` `break value` and `continue value` are errors in `for...do` context — there is no collection to contribute to. ### 14.10.4 For-Yield The `for...yield` expression builds collections: ```ori for n in numbers if n > 0 yield n * n; ``` See [Patterns § For-Yield Comprehensions](15-patterns.md#159-for-yield-comprehensions) for complete semantics including type inference, nested comprehensions, and break/continue with values. ### 14.10.5 Labeled For Labels enable break/continue to target outer loops: ```ori for:outer x in xs do for y in ys do if done(x, y) then break:outer; ``` See [Control Flow § Labeled Loops](16-control-flow.md#163-labeled-loops) for label semantics. ## 14.11 Loop Expression > **Grammar:** See [grammar.ebnf](grammar.md) § loop_expr The `loop { }` expression repeatedly evaluates its body until a `break` is encountered. ### 14.11.1 Syntax ```ori loop {body} loop:name { body } // labeled ``` ### 14.11.2 Body The body is a block expression. Use `{ }` for multiple expressions: ```ori // Single expression loop {process_next()} // Multiple expressions loop { let x = compute(); if done(x) then break x; update(x); } ``` ### 14.11.3 Loop Type The type of a `loop` expression is determined by its break values: - **Break with value**: Loop type is the break value type - **Break without value**: Loop type is `void` - **No break**: Loop type is `Never` (infinite loop) ```ori let result: int = loop { let x = compute(); if x > 100 then break x }; // type: int loop { let msg = receive(); if is_shutdown(msg) then break; process(msg); } // type: void @server () -> Never = loop {handle_request()}; // type: Never ``` ### 14.11.4 Multiple Break Paths All break paths shall produce compatible types: ```ori loop { if a then break 1; // int if b then break "two"; // error E0860: expected int, found str } ``` ### 14.11.5 Continue `continue` skips the rest of the current iteration: ```ori loop { let item = next(); if is_none(item) then break; if skip(item.unwrap()) then continue; process(item.unwrap()); } ``` `continue value` in a loop is an error (E0861). Loops do not accumulate values. ### 14.11.6 Labeled Loops Labels allow `break` and `continue` to target a specific loop. See [Control Flow § Labeled Loops](16-control-flow.md#163-labeled-loops) for label semantics. ## 14.12 While Expression > **Grammar:** See [grammar.ebnf](grammar.md) § while_expr The `while` expression evaluates a condition before each iteration. If the condition is `false`, the loop exits. ### 14.12.1 Syntax ```ori while condition do body while:name condition do body // labeled ``` ### 14.12.2 Desugaring `while condition do body` desugars to: ```ori loop { if !condition then break; body } ``` The desugaring is purely syntactic — no new runtime semantics. ### 14.12.3 Type `while...do` has type `void`. It does not produce a value. `break value` inside a `while` loop is a compile-time error (E0860). `continue value` is a compile-time error (E0861). Use `loop { }` for value-producing loops. ### 14.12.4 Examples ```ori while self.pos < self.buf.len() do { self.pos += 1 } while self.pos < self.buf.len() && self.buf[self.pos].is_whitespace() do self.pos += 1 ``` ## 14.13 Lambda ```ori x -> x * 2; (x, y) -> x + y; (x: int) -> int = x * 2; ``` ## 14.14 Evaluation Expressions are evaluated left-to-right. This order is guaranteed and observable. ### 14.14.1 Operand Evaluation Binary operators evaluate the left operand before the right: ```ori left() + right(); // left() called first, then right() ``` ### 14.14.2 Argument Evaluation Function arguments are evaluated left-to-right as written, before the call: ```ori foo(a: first(), b: second(), c: third()); // Order: first(), second(), third(), then foo() ``` Named arguments evaluate in written order, not parameter order: ```ori foo(c: third(), a: first(), b: second()); // Order: third(), first(), second(), then foo() ``` ### 14.14.3 Compound Expressions Postfix operations evaluate left-to-right: ```ori list[index()].method(arg()); // Order: list, index(), method lookup, arg(), method call ``` ### 14.14.4 List and Map Literals Elements evaluate left-to-right: ```ori [first(), second(), third()]; {"a": first(), "b": second()}; ``` ## 14.15 Pipe Operator > **Grammar:** See [grammar.ebnf](grammar.md) § `pipe_expr`, `pipe_step` The _pipe operator_ `|>` enables left-to-right function composition. The left operand is evaluated and passed as an argument to the right operand. ```ori data |> filter(predicate: x -> x > 0) |> map(transform: x -> x * 2) |> sum ``` ### 14.15.1 Implicit Fill When the right side of `|>` is a function call, the piped value fills the single _unspecified parameter_. A parameter is unspecified when it is both (a) not provided in the call arguments and (b) has no default value. ```ori // max_pool2d has params: (input: Tensor, kernel_size: int) -> Tensor x |> max_pool2d(kernel_size: 2) // Fills: max_pool2d(input: x, kernel_size: 2) ``` It is a compile-time error if: - Zero parameters are unspecified (all provided or defaulted): "nothing for pipe to fill" - Two or more parameters are unspecified: "ambiguous pipe target; specify all parameters except one" ### 14.15.2 Method Calls on the Piped Value A leading `.` calls a method on the piped value itself, rather than passing it as a function argument: ```ori x |> .flatten(start_dim: 1) // x.flatten(start_dim: 1) x |> .sort() // x.sort() ``` Without the dot, the pipe step is a free function call with implicit fill: ```ori x |> sort // free function: sort(: x) x |> .sort() // method: x.sort() ``` ### 14.15.3 Lambda Pipe Steps A lambda receives the piped value as its parameter: ```ori x |> (a -> a @ weight + bias) x |> (a -> a ** 2) ``` ### 14.15.4 Error Propagation The `?` operator on a pipe step applies to the result of the desugared call: ```ori data |> parse_csv? // Desugars to: parse_csv(input: data)? ``` ### 14.15.5 Desugaring Each pipe step desugars to a let-binding and an ordinary call: ```ori expr |> func(arg: val) // Desugars to: { let $__pipe = expr; func(: __pipe, arg: val) } expr |> .method(arg: val) // Desugars to: { let $__pipe = expr; __pipe.method(arg: val) } ``` The type checker resolves implicit fill by inspecting the function signature. The evaluator and codegen see only the desugared form. ### 14.15.6 Precedence and Associativity `|>` has the lowest precedence of all binary operators (level 16, below `??` at 15). It is left-associative. ```ori a + b |> process // (a + b) |> process a |> f |> g |> h // ((a |> f) |> g) |> h — equivalent to h(g(f(a))) ``` --- ## 14.16 Spread Operator > **Grammar:** See [grammar.ebnf](grammar.md) § EXPRESSIONS (list_element, map_element, struct_element) The spread operator `...` expands collections and structs in literal contexts. ### 14.16.1 List Spread Expands list elements into a list literal: ```ori let a = [1, 2, 3]; let b = [4, 5, 6]; [...a, ...b]; // [1, 2, 3, 4, 5, 6] [0, ...a, 10]; // [0, 1, 2, 3, 10] [first, ...middle, last]; ``` The spread expression shall be of type `[T]` where `T` matches the list element type. ### 14.16.2 Map Spread Expands map entries into a map literal: ```ori let defaults = {"timeout": 30, "retries": 3}; let custom = {"retries": 5, "verbose": true}; {...defaults, ...custom}; // {"timeout": 30, "retries": 5, "verbose": true} ``` Later entries override earlier ones on key conflicts. The spread expression shall be of type `{K: V}` matching the map type. ### 14.16.3 Struct Spread Copies fields from an existing struct: ```ori type Point = { x: int, y: int, z: int } let original = Point { x: 1, y: 2, z: 3 }; Point { ...original, x: 10 }; // Point { x: 10, y: 2, z: 3 } Point { x: 10, ...original }; // Point { x: 1, y: 2, z: 3 } ``` Order determines precedence: later fields override earlier ones. The spread expression shall be of the same struct type. ### 14.16.4 Constraints - Spread is only valid in literal contexts (lists, maps, struct constructors) - It is a compile-time error to use spread in function call arguments - All spread expressions shall have compatible types with the target container - Struct spread requires the exact same type (not subtypes or supertypes) ### 14.16.5 Evaluation Order Spread expressions evaluate left-to-right: ```ori [first(), ...middle(), last()] // Order: first(), middle(), last() {...defaults(), "key": computed(), ...overrides()} // Order: defaults(), computed(), overrides() ``` ### 14.16.6 Assignment The right side evaluates before assignment: ```ori x = compute(); // compute() evaluated, then assigned to x ``` ### 14.16.7 Compound Assignment > **Grammar:** See [grammar.ebnf](grammar.md) § `compound_op` > **Rules:** See [operator-rules.md](operator-rules.md) § Compound Assignment A _compound assignment_ `x op= y` desugars to `x = x op y` at parse time. The left-hand side shall be a mutable binding. Compound assignment is a statement, not an expression. ```ori x += 1; // desugars to: x = x + 1 point.x *= scale; // desugars to: point.x = point.x * scale flags |= MASK; // desugars to: flags = flags | MASK passed &&= check(); // desugars to: passed = passed && check() ``` Supported operators: `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, `@=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, `&&=`, `||=`. The `&&=` and `||=` forms preserve short-circuit evaluation: `x &&= expr` does not evaluate `expr` when `x` is `false`. ### 14.16.8 Short-Circuit Evaluation Logical and coalesce operators may skip the right operand: | Operator | Skips right when | |----------|------------------| | `&&` | Left is `false` | | `\|\|` | Left is `true` | | `??` | Left is `Some`/`Ok` | ```ori false && expensive(); // expensive() not called true \|\| expensive(); // expensive() not called Some(x) ?? expensive(); // expensive() not called ``` ### 14.16.9 Conditional Branches Only the taken branch is evaluated: ```ori if condition then only_if_true() else only_if_false() ``` See [Control Flow](16-control-flow.md) for details on conditionals and loops. ## 14.17 Composite Literal Typing ### 14.17.1 List literals The type of a list literal `[e1, e2, ..., en]` is `[T]` where `T` is the unified type of all elements. All elements shall have the same type; there are no implicit numeric conversions. ```ori [1, 2, 3] // type: [int] ["a", "b"] // type: [str] [1, "hello"] // error: cannot unify int and str [1, 2.0] // error: no implicit int → float conversion [[1, 2], [3, 4]] // type: [[int]] ``` An empty list literal `[]` requires type context for inference. Without context, it is a compile-time error. ```ori let x: [int] = []; // OK: type context provides [int] let y = []; // error: cannot infer element type ``` ### 14.17.2 Map literals The type of a map literal `{k1: v1, k2: v2, ...}` is `{K: V}` where `K` is the unified key type and `V` is the unified value type. Keys shall implement `Eq` and `Hashable`. ```ori {"name": "Alice", "city": "NYC"} // type: {str: str} {1: "one", 2: "two"} // type: {int: str} ``` The syntax `{ }` (braces with whitespace) is parsed as an empty map literal. NOTE When key expressions are bare identifiers (e.g., `{key: value}`), the parser distinguishes map literals from struct literals by the presence or absence of a type name prefix. ### 14.17.3 Struct literals The type of a struct literal `TypeName { f1: v1, f2: v2 }` is `TypeName`. All fields shall be provided unless a spread expression (`...`) supplies the remaining fields. Field types shall match the declared field types. An unknown field name is a compile-time error. A missing required field is a compile-time error. ### 14.17.4 Tuple literals The type of a tuple literal `(e1, e2, ..., en)` is `(T1, T2, ..., Tn)` where each `Ti` is the type of the corresponding element. The empty tuple `()` has type `void`. NOTE Single-element tuples are not supported. `(a,)` is parsed as a parenthesized expression. ## 14.18 Template String Semantics A template string `` `text {expr} text` `` desugars to string concatenation of its segments. Each interpolated expression is converted to `str` by calling its `Printable.to_str()` method. When a format specifier is present, the expression shall implement `Formattable`, and the interpolation desugars to a call to `Formattable.format(self:, spec:)`. Segments are evaluated left-to-right. Nested template strings are valid. EXAMPLE `` `Hello, {name}!` `` desugars to `"Hello, " + name.to_str() + "!"`. ## 14.19 General Evaluation Order Expressions are evaluated left-to-right. In a binary expression `a + b`, `a` is evaluated before `b`. In a function call `f(x: a, y: b)`, `a` is evaluated before `b`. Arguments are evaluated in textual order regardless of parameter names. Exceptions to left-to-right evaluation: - Short-circuit operators (`&&`, `||`, `??`) may skip the right operand (see 14.15.8). - Conditional branches evaluate only the taken branch (see 14.15.9). ## 14.20 Method Values Methods cannot be referenced without calling them. The expression `value.method` without trailing parentheses is a compile-time error. ```ori let f = list.contains; // error: method reference not supported let g = list.contains(value: 42); // OK: method call ``` NOTE Associated functions may be referenced as values by their qualified name. See [Clause 10](10-declarations.md#1041-associated-functions). --- --- title: "Patterns" description: "Clause 15: Ori Language Specification — Patterns" order: 15 section: "Language" --- # 15 Patterns Compiler-level control flow and concurrency constructs. > **Grammar:** See [grammar.ebnf](grammar.md) § PATTERNS ## 15.1 Categories | Category | Patterns | Purpose | |----------|----------|---------| | Block expressions | `{ }` blocks, `try { }`, `match expr { }` | Sequential expressions, error propagation, pattern matching | | `function_exp` | `recurse`, `parallel`, `spawn`, `timeout`, `cache`, `with`, `for`, `catch` | Concurrency, recursion, resources, error recovery | | `function_val` | `int`, `float`, `str`, `byte` | Type conversion | NOTE Data transformation (`map`, `filter`, `fold`, `find`, `collect`) and resilience (`retry`, `validate`) are stdlib methods, not compiler patterns. See [Built-in Functions](annex-c-built-in-functions.md). ## 15.2 Block Expressions ### 15.2.1 Blocks A block `{ }` is a sequence of `;`-terminated statements. The last expression without `;` is the block's value. A block where every expression has `;` is a void block. ```ori { let x = compute(); let y = transform(x); x + y } ``` #### Function-Level Contracts: `pre()` / `post()` Contracts are declared on the function, between the return type and `=`: ```ori @divide (a: int, b: int) -> int pre(b != 0) post(r -> r * b <= a) = a div b; // Multiple conditions @transfer (from: Account, to: Account, amount: int) -> (Account, Account) pre(amount > 0 | "amount must be positive") pre(from.balance >= amount | "insufficient funds") post((f, t) -> f.balance + t.balance == from.balance + to.balance) = { let new_from = Account { balance: from.balance - amount, ..from }; let new_to = Account { balance: to.balance + amount, ..to }; (new_from, new_to) } ``` **Semantics**: 1. Evaluate all `pre()` conditions in order; panic on failure 2. Execute function body 3. Bind result to each `post()` lambda parameter 4. Evaluate all `post()` conditions in order; panic on failure 5. Return result **Scope constraints**: - `pre()` may only reference function parameters and module-level bindings - `post()` may reference the result (via lambda parameter) plus everything visible to `pre()` **Type constraints**: - `pre()` condition shall have type `bool` - `post()` shall be a lambda from result type to `bool` - It is a compile-time error to use `post()` on a function returning `void` **Custom messages**: Use `condition | "message"` to provide a custom panic message. Without a message, the compiler embeds the condition's source text. ### 15.2.2 try Error-propagating sequence. Returns early on `Err`. ```ori try { let content = read_file(path); let parsed = parse(content); Ok(transform(parsed)) } ``` ### 15.2.3 match ```ori match status { Pending -> "waiting", Running(p) -> str(p) + "%", x if x > 0 -> "positive", _ -> "other", } ``` Arms are comma-separated. Trailing commas are optional. Multi-expression arm bodies use blocks: ```ori match request { Get(url) -> { let response = fetch(url: url); Ok(response) }, Post(url, body) -> { let result = send(url: url, body: body); Ok(result) }, _ -> Err("unsupported"), } ``` Match patterns include: literals, identifiers, wildcards (`_`), variant patterns, struct patterns, list patterns with rest (`..`), or-patterns (`|`), at-patterns (`@`), and range patterns. Match shall be exhaustive. #### At-Patterns > **Grammar:** See [grammar.ebnf](grammar.md) § at_pattern An _at-pattern_ binds the entire matched value to a name while simultaneously matching against an inner pattern. ```ebnf at_pattern = identifier "@" match_pattern . ``` The identifier before `@` binds the whole scrutinee value. The pattern after `@` shall match for the arm to be selected. Both the identifier and any bindings in the inner pattern are in scope in the arm's body. ```ori match opt { whole @ Some(inner) -> use_both(whole: whole, inner: inner), None -> default_value(), }; ``` Here `whole` has type `Option` (the full value) and `inner` has type `T` (the unwrapped payload). The arm matches only when the scrutinee is `Some(_)`. At-patterns compose with all other match patterns: ```ori // With variant patterns match status { s @ Failed(_) -> log_and_report(status: s), _ -> "ok", }; // With struct patterns match point { p @ { x, y } -> transform(point: p, dx: x, dy: y), }; // With nested at-patterns match tree { node @ Branch(left @ Leaf(_), _) -> prune(tree: node, leaf: left), other -> other, }; ``` The type of the at-pattern binding is the type of the scrutinee at that nesting level, not the type of the inner pattern's bindings. At-patterns are refutable if their inner pattern is refutable, and irrefutable if their inner pattern is irrefutable. #### Range Patterns > **Grammar:** See [grammar.ebnf](grammar.md) § range_pattern, const_pattern A _range pattern_ matches values within a numeric or character range. Both exclusive (`..`) and inclusive (`..=`) forms are supported. ```ebnf range_pattern = const_pattern ( ".." | "..=" ) const_pattern . const_pattern = literal_pattern | "$" identifier . ``` A range pattern `lo..hi` matches a value `v` when `v >= lo && v < hi`. A range pattern `lo..=hi` matches when `v >= lo && v <= hi`. Both endpoints shall be compile-time constants (literals or `$`-prefixed constant identifiers). Range patterns are valid for types that implement `Comparable` with a discrete domain: `int`, `char`, and `byte`. Float range patterns are not permitted. EXAMPLE 1 Range patterns on integer, char, and byte types: ```ori match score { 0..60 -> "fail", 60..=100 -> "pass", _ -> "invalid", } match ch { 'a'..='z' | 'A'..='Z' | '_' -> "ident_start", '0'..='9' -> "digit", _ -> "other", } match b: byte { b'0'..=b'9' -> "digit", b'a'..=b'f' | b'A'..=b'F' -> "hex_letter", _ -> "other", } ``` EXAMPLE 2 Constant endpoints: ```ori let $MIN_PRINTABLE = b' '; let $MAX_PRINTABLE = b'~'; match b { $MIN_PRINTABLE..=$MAX_PRINTABLE -> "printable", _ -> "control", } ``` Both endpoints shall have the same type. The compiler warns about empty range patterns (where `lo > hi`) and overlapping range patterns. Range patterns compose with or-patterns (`|`) and at-patterns (`@`). #### Exhaustiveness Checking A match expression is _exhaustive_ if every possible value of the scrutinee type matches at least one pattern arm. The compiler uses pattern matrix decomposition to verify exhaustiveness. For each type, the compiler knows its constructors: - `bool`: `true`, `false` - `Option`: `Some(_)`, `None` - `Result`: `Ok(_)`, `Err(_)` - Sum types: all declared variants - `int`: infinite (requires wildcard) - `byte`: finite (0..=255, range patterns can cover) - `char`: finite but large (requires wildcard in practice) - `str`: infinite (requires wildcard) **Never variants:** Variants containing `Never` are uninhabited and need not be matched: ```ori type MaybeNever = Value(int) | Impossible(Never); match maybe { Value(v) -> v, // Impossible case can be omitted — it can never occur } ``` Matching `Never` explicitly is permitted but the arm is unreachable. Non-exhaustiveness is a compile-time error. There is no partial match construct. | Context | Non-Exhaustive | Rationale | |---------|---------------|-----------| | `match` expression | Error | Must handle all cases to return a value | | `let` binding destructure | Error | Must match to bind | | Function clause patterns | Error | All clauses together shall be exhaustive | #### Pattern Refutability An _irrefutable pattern_ always matches. A _refutable pattern_ may fail to match. Irrefutable patterns: - Wildcard (`_`) - Variable binding (`x`) - Struct with all irrefutable fields (`Point { x, y }`) - Tuple with all irrefutable elements (`(a, b)`) Refutable patterns: - Literals (`42`, `"hello"`) - Variants (`Some(x)`, `None`) - Ranges (`0..10`) - Lists with length (`[a, b]`) - Guards (`x if x > 0`) | Context | Requirement | |---------|-------------| | `match` arm | Any pattern (refutable OK) | | `let` binding | Must be irrefutable | | Function parameter | Must be irrefutable | | `for` loop variable | Must be irrefutable | #### Guards and Exhaustiveness Guards are not considered for exhaustiveness checking. The compiler cannot statically verify guard conditions. A match with guards shall include a catch-all pattern: ```ori // ERROR: guards require catch-all match n { x if x > 0 -> "positive", x if x < 0 -> "negative", // Error: patterns not exhaustive due to guards } // OK: catch-all ensures exhaustiveness match n { x if x > 0 -> "positive", x if x < 0 -> "negative", _ -> "zero", } ``` #### Or-Pattern Exhaustiveness Or-patterns contribute their combined coverage: ```ori type Light = Red | Yellow | Green; // Exhaustive via or-pattern match light { Red | Yellow -> "stop", Green -> "go", } ``` Bindings in or-patterns shall appear in all alternatives with the same type. #### At-Pattern Exhaustiveness At-patterns contribute the same coverage as their inner pattern: ```ori match opt { whole @ Some(x) -> use_both(whole: whole, inner: x), None -> default_value(), } ``` #### List Pattern Exhaustiveness List patterns match by length: | Pattern | Matches | |---------|---------| | `[]` | Empty list only | | `[x]` | Exactly one element | | `[x, y]` | Exactly two elements | | `[x, ..rest]` | One or more elements | | `[..rest]` | Any list (including empty) | To be exhaustive, patterns shall cover all lengths. #### Range Pattern Exhaustiveness `int` ranges cannot be exhaustive without a wildcard (infinite domain). `byte` ranges can be exhaustive (domain 0..=255). `char` ranges are theoretically exhaustive (Unicode scalar values) but practically require a wildcard. EXAMPLE Exhaustive byte range patterns: ```ori match b: byte { 0..128 -> "ascii", 128..=255 -> "non-ascii", } ``` The compiler warns about overlapping ranges. #### Unreachable Patterns The compiler warns about patterns that can never match due to earlier patterns covering all their cases. ## 15.3 Recursion (function_exp) ### 15.3.1 recurse The `recurse` pattern evaluates recursive computations with optional memoization and parallelism. ```ori recurse( condition: bool_expr, base: expr, step: expr_with_self, memo: bool = false, parallel: bool = false, ) ``` #### Evaluation 1. Evaluate `condition` 2. If true: return `base` expression 3. If false: evaluate `step` expression (which may contain `self(...)` calls) ```ori @factorial (n: int) -> int = recurse( condition: n <= 1, base: 1, step: n * self(n - 1), ); ``` #### Self Keyword `self(...)` within `step` represents a recursive invocation: ```ori @fibonacci (n: int) -> int = recurse( condition: n <= 1, base: n, step: self(n - 1) + self(n - 2), ); ``` Arguments to `self(...)` shall match the enclosing function's parameter arity. #### Self Scoping Within a `recurse` expression: - `self` (without parentheses) — trait method receiver (if applicable) - `self(...)` (with arguments) — recursive call These coexist when `recurse` appears in a trait method: ```ori impl TreeOps: Tree { @depth (self) -> int = recurse( condition: self.is_leaf(), // Receiver base: 1, step: 1 + max(left: self(self.left()), right: self(self.right())), // Recursive calls ); } ``` It is a compile-time error to use `self(...)` outside of a `recurse` step expression. #### Memoization With `memo: true`, results are cached for the duration of the top-level call: ```ori @fib (n: int) -> int = recurse( condition: n <= 1, base: n, step: self(n - 1) + self(n - 2), memo: true, // O(n) instead of O(2^n) ); ``` Memo requirements: - All parameters shall be `Hashable + Eq` - Return type shall be `Clone` The cache is created at top-level entry, shared across recursive calls, and discarded when the top-level call returns. #### Parallel Recursion With `parallel: true`, independent `self(...)` calls execute concurrently: ```ori @parallel_fib (n: int) -> int uses Suspend = recurse( condition: n <= 1, base: n, step: self(n - 1) + self(n - 2), parallel: true, ); ``` Parallel requirements: - Requires `uses Suspend` capability - Captured values shall be `Sendable` - Return type shall be `Sendable` When `memo: true` and `parallel: true` are combined, the memo cache is thread-safe. If multiple tasks request the same key simultaneously, one computes while others wait. #### Tail Call Optimization When `self(...)` is in tail position, the compiler optimizes to a loop with O(1) stack space: ```ori @sum_to (n: int, acc: int = 0) -> int = recurse( condition: n == 0, base: acc, step: self(n - 1, acc + n), // Tail position: compiled to loop ); ``` #### Stack Limits Non-tail recursive calls are limited to a depth of 1000. Exceeding this limit causes a panic. Tail-optimized recursion bypasses this limit. ## 15.4 Concurrency Concurrency patterns create tasks. See [Concurrency Model](22-concurrency-model.md) for task definitions, async context semantics, and capture rules. ### 15.4.1 parallel Execute tasks, wait for all to settle. Creates one task per list element. ```ori parallel( tasks: [() -> T uses Suspend], max_concurrent: Option = None, timeout: Option = None, ) -> [Result] ``` Returns `[Result]`. Never fails; errors captured in results. #### Execution Order Guarantees | Aspect | Guarantee | |--------|-----------| | Start order | Tasks start in list order | | Completion order | Any order (concurrent execution) | | Result order | Same as task list order | ```ori let results = parallel(tasks: [slow, fast, medium]); // results[0] = result of slow (first task) // results[1] = result of fast (second task) // results[2] = result of medium (third task) // Even if fast completed first ``` #### Concurrency Limits When `max_concurrent` is `Some(n)`: - At most `n` tasks run simultaneously - Tasks are queued in list order - When one completes, the next queued task starts When `max_concurrent` is `None` (default), all tasks may run simultaneously. #### Timeout Behavior When `timeout` expires: 1. Incomplete tasks are marked for cancellation 2. Tasks reach cancellation checkpoints and terminate 3. Cancelled tasks return `Err(CancellationError { reason: Timeout, task_id: n })` 4. Completed results are preserved Tasks can cooperatively check for cancellation using `is_cancelled()`. #### Resource Exhaustion If the runtime cannot allocate resources for a task: - The task returns `Err(CancellationError { reason: ResourceExhausted, task_id: n })` - Other tasks continue executing - The pattern does NOT panic #### Error Handling Errors do not stop other tasks. All tasks run to completion (equivalent to `CollectAll` behavior). For early termination on error, use `nursery` with `on_error: FailFast`. #### Empty Task List `parallel(tasks: [])` returns `[]` immediately. See [nursery](#1544-nursery) for cancellation semantics and `CancellationError` type. ### 15.4.2 spawn Fire-and-forget task execution. Creates one task per list element. ```ori spawn( tasks: [() -> T uses Suspend], max_concurrent: Option = None, ) ``` Returns `void` immediately. Task results and errors are discarded. #### Fire-and-Forget Semantics Tasks start and the pattern returns without waiting: ```ori spawn(tasks: [send_email(u) for u in users]); // Returns void immediately // Emails sent in background ``` Errors in spawned tasks are silently discarded. To log errors, handle them within the task: ```ori spawn(tasks: [ () -> match risky_operation() { Ok(_) -> log(msg: "success"), Err(e) -> log(msg: `failed: {e}`), }, ]) ``` #### Task Lifetime Spawned tasks: - Run independently of the spawning scope - May outlive the spawning function (true fire-and-forget) - Complete naturally, are cancelled on program exit, or terminate on panic NOTE `spawn` is the ONLY concurrency pattern that allows tasks to escape their spawning scope. Unlike `parallel` and `nursery`, which guarantee all tasks complete before the pattern returns, `spawn` tasks are managed by the runtime. For structured concurrency with guaranteed completion, use `nursery`. ```ori @setup () -> void uses Suspend = { spawn(tasks: [background_monitor()]); // Function returns, but monitor continues } ``` #### Concurrency Control When `max_concurrent` is `Some(n)`, at most `n` tasks run simultaneously. When `None` (default), all tasks may start simultaneously. #### Resource Exhaustion If the runtime cannot allocate resources for a task: - The task is dropped - No error is surfaced (fire-and-forget semantics) - Other tasks continue ### 15.4.3 timeout Bounded execution time for an operation. ```ori timeout( op: expression, after: Duration, ) -> Result ``` #### Basic Behavior 1. Start executing `op` 2. If `op` completes before `after`: return `Ok(result)` 3. If `after` elapses first: cancel `op`, return `Err(CancellationError { reason: Timeout, task_id: 0 })` ```ori let result = timeout(op: fetch(url), after: 5s); // result: Result ``` #### Cancellation When timeout expires, the operation is cooperatively cancelled using the same cancellation model as `nursery`: 1. Operation is marked for cancellation 2. At the next cancellation checkpoint, operation terminates 3. Destructors run during unwinding 4. `Err(CancellationError { reason: Timeout, task_id: 0 })` is returned Cancellation checkpoints: - Suspending function calls (functions with `uses Suspend`) - Loop iterations - Pattern entry (`run`, `try`, `match`, etc.) CPU-bound operations without checkpoints cannot be cancelled until they reach one. #### Nested Timeout Inner timeouts can be shorter than outer: ```ori timeout( op: { let a = timeout(op: step1(), after: 2s)?; let b = timeout(op: step2(), after: 2s)?; (a, b) }, after: 5s, // Overall timeout ) ``` ### 15.4.4 nursery Structured concurrency with guaranteed task completion. Creates tasks via `n.spawn()`. ```ori nursery( body: n -> for item in items do n.spawn(task: () -> process(item)), on_error: CollectAll, timeout: 30s, ) ``` | Parameter | Type | Description | |-----------|------|-------------| | `body` | `Nursery -> T` | Lambda that spawns tasks | | `on_error` | `NurseryErrorMode` | Error handling mode | | `timeout` | `Duration` | Maximum time (optional) | Returns `[Result]`. All spawned tasks complete before nursery exits. The `Nursery` type provides a single method: ```ori type Nursery = { @spawn (self, task: () -> T uses Suspend) -> void; } ``` Error modes: ```ori type NurseryErrorMode = CancelRemaining | CollectAll | FailFast; ``` | Mode | Behavior | |------|----------| | `CancelRemaining` | On first error, cancel pending tasks; running tasks continue | | `CollectAll` | Wait for all tasks regardless of errors (no cancellation) | | `FailFast` | On first error, cancel all tasks immediately | Guarantees: - No orphan tasks — all spawned tasks complete or cancel - Error propagation — task failures captured in results - Scoped concurrency — tasks cannot escape nursery scope #### Cancellation Model Ori uses **cooperative cancellation**. A cancelled task: 1. Is marked for cancellation 2. Continues executing until it reaches a cancellation checkpoint 3. At the checkpoint, terminates with `CancellationError` 4. Runs cleanup/destructors during termination Cancellation checkpoints: - Suspension points (async calls, channel operations) - Loop iterations (start of each `for` or `loop` iteration) - Pattern entry (`run`, `try`, `match`, `parallel`, `nursery`) #### Cancellation Types ```ori type CancellationError = { reason: CancellationReason, task_id: int, } type CancellationReason = | Timeout | SiblingFailed | NurseryExited | ExplicitCancel | ResourceExhausted; ``` #### Cancellation API The `is_cancelled()` built-in function returns `bool`, available in async contexts: ```ori @long_task () -> Result uses Suspend = { for item in items do { if is_cancelled() then break Err(CancellationError { ... }); process(item) }; Ok(result) } ``` The `for` loop automatically checks cancellation at each iteration when inside an async context. #### Cleanup Guarantees When a task is cancelled: 1. Stack unwinding occurs from the cancellation checkpoint 2. Destructors run for all values in scope 3. Cleanup is guaranteed to complete before task terminates ## 15.5 Resource Management (function_exp) ### 15.5.1 cache Memoization with TTL-based expiration. Requires `Cache` capability. ```ori cache( key: expression, op: expression, ttl: Duration, ) ``` #### Semantics 1. Compute `key` expression 2. Check cache for existing unexpired entry 3. If hit: return cached value (clone) 4. If miss: evaluate `op`, store result, return it ```ori @fetch_user (id: int) -> User uses Cache = cache( key: `user-{id}`, op: db.query(id: id), ttl: 5m, ); ``` The `cache` pattern returns the same type as `op`. #### Key Requirements Keys shall implement `Hashable` and `Eq`: ```ori cache(key: "string-key", op: ..., ttl: 1m); // OK: str is Hashable + Eq cache(key: 42, op: ..., ttl: 1m); // OK: int is Hashable + Eq cache(key: (user_id, "profile"), op: ..., ttl: 1m); // OK: tuple of hashables ``` #### Value Requirements Cached values shall implement `Clone`. The cache returns a clone of the stored value. #### TTL Behavior | TTL | Behavior | |-----|----------| | Positive | Entry expires after TTL from creation | | Zero | No caching (always recompute) | | Negative | Compile error (E0992) | #### Concurrent Access When multiple tasks request the same key simultaneously (stampede prevention): 1. First request computes the value 2. Other requests wait for computation 3. All receive the same result If `op` fails during stampede, waiting requests also receive the error. Failed results are NOT cached. #### Error Handling If `op` returns `Err` or panics, the result is NOT cached. To cache error results, wrap in a non-error type: ```ori cache( key: url, op: match fetch(url) { r -> r }, // Cache the Result itself ttl: 5m, ) ``` #### Invalidation Time-based expiration is automatic. Manual invalidation uses `Cache` capability methods: ```ori @invalidate_user (id: int) -> void uses Cache = Cache.invalidate(key: `user-{id}`); @clear_all_cache () -> void uses Cache = Cache.clear(); ``` #### Cache vs Memoization The `cache` pattern and `recurse(..., memo: true)` serve different purposes: | Aspect | `cache(...)` | `recurse(..., memo: true)` | |--------|--------------|---------------------------| | Persistence | TTL-based, may persist across calls | Call-duration only | | Capability | Requires `Cache` | Pure, no capability | | Scope | Shared across function calls | Private to single recurse | | Use case | API responses, config, expensive I/O | Pure recursive algorithms | #### Error Codes | Code | Description | |------|-------------| | E0990 | Cache key must be `Hashable` | | E0991 | `cache` requires `Cache` capability | | E0992 | TTL must be non-negative | ### 15.5.2 with Resource management with guaranteed cleanup. ```ori with( acquire: open_file(path), action: f -> read_all(f), release: f -> close(f), ) ``` NOTE The property is named `action:` because `use` is a reserved keyword. #### Semantics 1. Evaluate `acquire:` to obtain resource 2. If `acquire` fails (returns `Err` or panics), stop—no cleanup needed 3. If `acquire` succeeds, bind result to `action:` parameter 4. Evaluate `action:` expression 5. Always evaluate `release:` with the resource, regardless of how `action:` completed The pattern returns the result of `action:`. #### Release Guarantee If `acquire:` succeeds, `release:` runs under all exit conditions: | Exit Condition | Release Runs | |----------------|--------------| | Normal completion | Yes | | Panic during action | Yes | | Error propagation (`?`) | Yes | | `break` in action | Yes | | `continue` in action | Yes | ```ori with( acquire: open_file(path), action: f -> { if bad_condition then panic("abort"); if err_condition then Err("failed")?; read_all(f) }, release: f -> close(f), // Always called ) ``` #### Type Constraints The `release:` expression shall return `void`: ```ori // OK: release returns void with(acquire: lock(), action: l -> work(), release: l -> l.unlock()) // Error E0861: release must return void with(acquire: lock(), action: l -> work(), release: l -> l.count()) ``` #### Result Types When `acquire:` returns `Result`: ```ori @open_file (path: str) -> Result; // Using `?` for fallible acquire: @read_config (path: str) -> Result = with( acquire: open_file(path)?, // Propagates Err, no cleanup needed action: f -> parse(f.read_all()), release: f -> f.close(), ); ``` When `action:` may fail: ```ori @process_file (path: str) -> Result uses FileSystem = with( acquire: open_file(path)?, action: f -> { let content = f.read_all()?; // May propagate Err parse(content)? // May propagate Err }, release: f -> f.close(), // Still runs on any Err ); ``` #### Double Fault Abort If `release:` panics while already unwinding (e.g., after `action:` panicked), the program aborts immediately: - The `@panic` handler is NOT called - Both panic messages are shown - Exit code is non-zero This prevents cascading failures during cleanup. #### Suspending Context In a suspending context (`uses Suspend`), both `action:` and `release:` may suspend: ```ori @with_connection (url: str) -> Data uses Suspend = with( acquire: connect(url), action: conn -> fetch_data(conn), // May suspend release: conn -> conn.close(), // May suspend ); ``` #### Error Codes | Code | Description | |------|-------------| | E0860 | `with` pattern missing required parameter (`acquire:`, `action:`, or `release:`) | | E0861 | `release` must return `void` | ## 15.6 Error Recovery (function_exp) ### 15.6.1 catch Captures panics and converts them to `Result`. ```ori catch(expr: may_panic()); ``` If the expression evaluates successfully, returns `Ok(value)`. If the expression panics, returns `Err(message)` where `message` is the panic message string. See [Errors and Panics § Catching Panics](17-errors-and-panics.md#174-catching-panics). ## 15.7 for Pattern ```ori for(over: items, match: Some(x) -> x, default: 0); for(over: items, map: parse, match: Ok(v) -> v, default: fallback); ``` Returns first match or default. ## 15.8 For Loop Desugaring The `for` loop desugars to use the `Iterable` and `Iterator` traits: ```ori // This: for x in items do process(x: x); // Desugars to: { let iter = items.iter(); loop { match iter.next() { (Some(x), next_iter) -> { process(x: x); iter = next_iter; continue }, (None, _) -> break, } } } ``` ## 15.9 For-Yield Comprehensions The `for...yield` expression builds collections from iteration. ### 15.9.1 Basic Syntax ```ori for x in items yield expression; ``` Desugars to: ```ori items.iter().map(transform: x -> x * 2).collect(); ``` ### 15.9.2 Type Inference The result type is inferred from context: ```ori let numbers: [int] = for x in items yield x.id; // [int] let set: Set = for x in items yield x.name; // Set ``` Without context, defaults to list: ```ori let result = for x in 0..5 yield x * 2; // [int] ``` ### 15.9.3 Filtering A single `if` clause filters elements. Use `&&` for multiple conditions: ```ori for x in items if x > 0 yield x; for x in items if x > 0 && x < 100 yield x; ``` Desugars to: ```ori items.iter().filter(predicate: x -> x > 0).map(transform: x -> x).collect(); ``` ### 15.9.4 Nested Comprehensions Multiple `for` clauses produce a flat result: ```ori for x in xs for y in ys yield (x, y); ``` Desugars to: ```ori xs.iter().flat_map(transform: x -> ys.iter().map(transform: y -> (x, y))).collect(); ``` Each clause can have its own filter: ```ori for x in xs if x > 0 for y in ys if y > 0 yield x * y; ``` ### 15.9.5 Break and Continue In yield context, `break` and `continue` control collection building: | Statement | Effect | |-----------|--------| | `continue` | Skip element, add nothing | | `continue value` | Add `value` instead of yield expression | | `break` | Stop iteration, return results so far | | `break value` | Add `value` and stop | ```ori for x in items yield if skip(x) then continue, if done(x) then break x, transform(x), ``` ### 15.9.6 Map Collection Maps implement `Collect<(K, V)>`. Yielding 2-tuples collects into a map: ```ori let by_id: {int: User} = for user in users yield (user.id, user); ``` If duplicate keys are yielded, later values overwrite earlier ones. ### 15.9.7 Empty Results Empty source or all elements filtered produces an empty collection: ```ori for x in [] yield x * 2; // [] for x in [1, 2, 3] if x > 10 yield x; // [] ``` See [Types § Iterator Traits](08-types.md#813-iterator-traits) for trait definitions. --- --- title: "Control Flow" description: "Clause 16: Ori Language Specification — Control Flow" order: 16 section: "Language" --- # 16 Control flow Control flow determines the order of expression evaluation and how execution transfers between expressions. Ori is expression-based: all control flow constructs are expressions that produce values. The distinction between "statement" and "expression" is positional — any expression terminated by `;` is used as a statement (its value is discarded). ## 16.0 Statements and expressions ### 16.0.1 Expression statements An _expression statement_ is an expression evaluated for its side effects. The result value is discarded. An expression statement is terminated by `;` within a block. ```ori print(msg: "hello"); // evaluated for side effect, result discarded ``` ### 16.0.2 Result expressions The last expression in a block, if not terminated by `;`, is the _result expression_. Its value becomes the value of the block. See [7.8.1](07-lexical-elements.md#781-block-semicolons). ### 16.0.3 Statement-only constructs The following constructs always have type `void` and are used only as statements: - `let` bindings - `use` imports - Assignments (`x = v`) - Compound assignments (`x += v`) ### 16.0.4 Value-producing expressions All other constructs are expressions that produce values: | Expression | Type | |------------|------| | `if c then a else b` | Unified type of `a` and `b` | | `if c then a` | `void` (or `Never` if `a` is `Never`) | | `match expr { arms }` | Unified type of all arm bodies | | `for x in s yield e` | `[T]` where `T` is type of `e` | | `for x in s do e` | `void` | | `while c do e` | `void` | | `loop { ... break v }` | Type of `v` | | `loop { ... break }` | `void` | | `loop { ... }` (no break) | `Never` | | `block:name { ... break:name v }` | Type of `v` | | `try { ... }` | `Result` | ## 16.1 Sequential flow Expressions in a block `{ }` evaluate top to bottom. Each expression completes before the next begins. ```ori { let x = 1; let y = 2; x + y } ``` If any expression terminates early (via `break`, `continue`, `?`, or panic), subsequent expressions are not evaluated. ## 16.2 Loop control ### 16.2.1 Break `break` exits the innermost enclosing loop. ```ori loop { if done then break; process() } ``` `break` may include a value. The loop expression evaluates to this value: ```ori let result = loop { let x = compute(); if x > 100 then break x } // result = first x greater than 100 ``` A `break` without a value in a context requiring a value is an error. ### 16.2.2 Continue `continue` skips to the next iteration of the innermost enclosing loop. ```ori for x in items do { if x < 0 then continue; process(x); } ``` In `for...yield`, `continue` without a value skips the element: ```ori for x in items yield { if x < 0 then continue; // element not added x * 2 } ``` `continue` with a value uses that value for the current iteration: ```ori for x in items yield { if x < 0 then continue 0; // use 0 instead x * 2 } ``` ### 16.2.3 Continue in loop In `loop { }`, `continue` skips to the next iteration. `continue value` is an error (E0861) — loops do not accumulate values: ```ori loop { if skip then continue; // OK: start next iteration if bad then continue 42; // error E0861: loop doesn't collect process() } ``` ## 16.2.4 While loop A `while` expression evaluates a condition before each iteration. If the condition is `false`, the loop exits. > **Grammar:** See [Annex A](grammar.md) § `while_expr` `while condition do body` desugars to: ```ori loop { if !condition then break; body } ``` The condition expression shall have type `bool`. The `while` expression has type `void`. ```ori while self.pos < self.buf.len() do { self.pos += 1 } ``` `break` exits the loop. `break value` is a compile-time error (E0860) — `while...do` does not produce a value. `continue` skips to the next iteration (re-evaluating the condition). `continue value` is a compile-time error (E0861) — `while` does not accumulate values. Labels work on `while` loops as on `loop` and `for`: ```ori while:outer scanning do { while self.pos < self.buf.len() do { if done then break:outer } } ``` NOTE There is no `while...yield` form. Use `for...yield` with an iterator for collection building. ## 16.3 Labeled loops Labels allow `break` and `continue` to target an outer loop. ### 16.3.1 Label declaration Labels use the syntax `loop:name`, `while:name`, or `for:name`, with no space around the colon: ```ori loop:outer { for x in items do if x == target then break:outer } for:outer x in items do for y in other do if done(x, y) then break:outer ``` ### 16.3.2 Label reference Reference labels with `break:name` or `continue:name`: ```ori loop:search { for x in items do if found(x) then break:search x } ``` With value: ```ori let result = loop:outer { for x in items do if match(x) then break:outer x None } ``` ### 16.3.3 Label scope A label is visible within the loop body it labels. Labels scope correctly through arbitrary nesting: ```ori loop:a { loop:b { loop:c { break:a; // OK: exits outermost break:b; // OK: exits middle break:c // OK: exits innermost } } } ``` There is no language-imposed limit on label nesting depth. ### 16.3.4 No label shadowing Labels cannot be shadowed within their scope: ```ori loop:outer { loop:outer { // ERROR E0871: label 'outer' already in scope ... } } ``` ### 16.3.5 Type consistency All `break` paths for a labeled loop shall produce values of the same type: ```ori let x: int = loop:outer { for item in items do { if a(item) then break:outer 1; // int if b(item) then break:outer "two"; // ERROR E0872: expected int, found str } 0 } ``` ### 16.3.6 Continue with value In `for...yield` context, `continue:name value` contributes `value` to the outer loop's collection: ```ori let results = for:outer x in xs yield for:inner y in ys yield { if special(x, y) then continue:outer x * y; // Contribute to outer transform(x, y) } ``` The value in `continue:label value` shall have the same type as the target loop's yield element type. When `continue:label value` exits an inner `for...yield` to contribute to an outer `for...yield`, the inner loop's partially-built collection is discarded. Only `value` is contributed to the outer loop for this iteration. In `for...do` context, `continue:name value` is an error — there is no collection to contribute to: ```ori for:outer x in xs do for y in ys do { if skip(x, y) then continue:outer 42; // ERROR E0873: for-do doesn't collect process(x, y); } ``` ### 16.3.7 Valid label names Labels follow identifier rules. They cannot be keywords: ```ori loop:search { } // OK loop:_private { } // OK loop:loop123 { } // OK loop:for { } // ERROR: 'for' is a keyword ``` ## 16.4 Labeled blocks A _labeled block_ is a block expression with a label, allowing early exit via `break:label value`. > **Grammar:** See [Annex A](grammar.md) § `labeled_block` ### 16.4.1 Syntax The syntax is `block:name { body }`, where `block` is a context-sensitive keyword recognized only before `:`: ```ori let x = block:done { if condition1 then break:done value1; if condition2 then break:done value2; default_value } ``` `block` is a valid identifier outside this position: ```ori let block = 5; // OK: identifier let x = block:done { 42 } // OK: labeled block ``` ### 16.4.2 Semantics `break:label value` exits the named block and produces `value` as the block's result. All `break:label` paths and the final expression shall have compatible types. The type of the labeled block is the unified type of all exit paths. ```ori @validate (input: Request) -> Result = block:done { if input.name.is_empty() then break:done Err(Error { message: "name required" }); Ok(ValidRequest { name: input.name }) } ``` ### 16.4.3 Unlabeled break is loop-only Bare `break` (without a label) inside a labeled block targets the innermost enclosing **loop**, not the block: ```ori loop { let x = block:result { if done then break; // exits the LOOP if found then break:result v; // exits the BLOCK default }; process(x) } ``` ### 16.4.4 Continue targeting a block `continue:label` targeting a labeled block is a compile-time error. Blocks do not iterate: ```ori block:result { continue:result; // ERROR: cannot continue a labeled block } ``` ### 16.4.5 Transparency to loop control flow Labeled blocks are transparent to `break` and `continue` targeting outer loops, in the same way as `try` blocks (see 16.7.3): ```ori for:search items in collection do { let result = block:check { if invalid(items) then continue:search; // OK: continues outer for loop if found(items) then break:search items; // OK: breaks outer for loop transform(items) }; process(result) } ``` ### 16.4.6 Nesting and label namespace Labeled blocks are nestable. Block labels share the label namespace with loop labels. The no-shadowing rule (16.3.4) applies across all labeled constructs: ```ori block:outer { block:inner { break:outer 1; // OK: exits outer block break:inner 2; // OK: exits inner block } } loop:name { block:name { } // ERROR E0871: label 'name' already in scope } ``` ## 16.5 Error propagation The `?` operator propagates errors and absent values. ### 16.5.1 On Result If the value is `Err(e)`, the enclosing function returns `Err(e)`: ```ori @load (path: str) -> Result = { let content = read_file(path)?; // Err propagates let data = parse(content)?; Ok(data) } ``` ### 16.5.2 On Option If the value is `None`, the enclosing function returns `None`: ```ori @find (id: int) -> Option = { let record = db.lookup(id)?; // None propagates Some(User { ...record }) } ``` The function's return type shall be compatible with the propagated type. ## 16.6 Terminating expressions A _terminating expression_ is an expression whose evaluation is guaranteed to not complete normally. Terminating expressions have type `Never`, which is compatible with any type (see [8.1.1](08-types.md)). The following are terminating expressions: 1. `panic(msg:)`, `todo()`, `unreachable()` — always terminate the program 2. `break` and `break value` — exit the enclosing loop or labeled block 3. `continue` and `continue value` — skip to the next iteration 4. `expr?` when the Err/None branch is taken — returns from the enclosing function 5. A block `{ ... e }` where the last expression `e` is terminating 6. `if c then t else e` where both `t` and `e` are terminating 7. `match expr { arms }` where every arm body is terminating 8. `loop { body }` with no `break` — an infinite loop with type `Never` ```ori let x: int = if condition then 42 else panic(msg: "unreachable"); // panic(...) has type Never, compatible with int ``` Code following a terminating expression within the same block is unreachable. The compiler should warn about unreachable code. ```ori { panic(msg: "fail"); let x = 42; // warning: unreachable code } ``` ## 16.7 Conditional evaluation ### 16.7.1 If-then-else The condition expression shall have type `bool`. Only the taken branch is evaluated. With `else`: both branches shall have compatible types. The type of the `if` expression is the unified type of the two branches. Without `else`: the then-branch shall have type `void` or `Never`. An `if` without `else` has type `void`. ```ori // With else — expression producing a value let x = if a > b then a else b; // Without else — statement (void) if debug then print(msg: "debug info"); // Chained else if if x > 0 then "positive" else if x < 0 then "negative" else "zero" ``` NOTE There is no `if let` syntax. Use `match` for destructuring conditionals. ### 16.7.2 Match The scrutinee expression is evaluated exactly once. Arms are tested top-to-bottom. The body of the first matching arm is evaluated; no further arms are tested. All arm bodies shall have compatible types. The type of the `match` expression is the unified type of all arm bodies. An arm with type `Never` is compatible with any other arm type. The compiler shall verify that the match is _exhaustive_: every possible value of the scrutinee type is covered by at least one arm. A non-exhaustive match is a compile-time error. Guards (`if`) are evaluated after the pattern matches. Guarded arms do not contribute to exhaustiveness; a catch-all pattern (`_` or binding) is required after guarded arms. Unreachable arms (patterns that are subsets of earlier patterns) produce a compiler warning. ```ori match value { Some(x) if x > 0 -> x, // guard: only positive Some(x) -> -x, // remaining Some values None -> 0, // exhaustive with this arm } ``` ### 16.7.3 Try blocks A `try` block wraps an expression in error-handling context. The `?` operator inside a `try` block propagates to the `try` boundary rather than the enclosing function. ```ori let result: Result = try { let $a = parse(input)?; let $b = validate($a)?; $a + $b }; ``` The type of a `try` block is `Result` where `T` is the block's value type and `E` is the error type from `?` operations. `break` and `continue` inside a `try` block target the enclosing loop (passing through the `try` boundary). ## 16.8 Short-circuit operators Logical operators may skip evaluation of the right operand: | Operator | Skips right when | |----------|------------------| | `&&` | Left is `false` | | `\|\|` | Left is `true` | | `??` | Left is not `None`/`Err` | ```ori valid && expensive(); // expensive() skipped if valid is false cached ?? compute(); // compute() skipped if cached is Some/Ok ``` ## 16.9 Iteration protocol A `for` expression desugars to calls on the `Iterable` and `Iterator` traits. `for x in source do body` desugars to: 1. Call `source.iter()` to obtain an iterator (via `Iterable` trait) 2. Call `iterator.next()` to obtain `(Option, Iterator)` 3. If `Some(value)`: bind `x = value`, evaluate body, go to step 2 4. If `None`: stop For `for x in source if guard do body`, the guard is evaluated after `x` is bound. If the guard evaluates to `false`, the iteration skips the body (implicit `continue`). For `for x in source yield expr`: - Each iteration appends the value of `expr` to an accumulating list - The result type is `[T]` where `T` is the type of `expr` - An empty source produces an empty list `[]` - `break` stops iteration and returns the accumulated values so far - `break value` appends `value` and returns - `continue` skips this element (nothing appended) - `continue value` appends `value` instead of the normal yield expression Nested `for...yield` composes as flat-map: ```ori for x in xs for y in ys yield (x, y) // Equivalent to: xs.flat_map(x -> ys.map(y -> (x, y))) ``` ### 16.9.1 For producing maps When a `for...yield` expression yields tuples of `(K, V)` and the target type is `{K: V}`, the result is a map: ```ori let m: {str: int} = for item in items yield (item.name, item.count); ``` ## 16.10 Break and continue summary | Form | Valid in | Effect | |------|---------|--------| | `break` | `loop`, `while...do`, `for...do`, `for...yield` | Exit loop | | `break value` | `loop`, `for...yield` | Exit with value | | `break:label` | Labeled `loop`, `while`, `for`, `block` | Exit labeled construct | | `break:label value` | Labeled `loop`, `for...yield`, `block` | Exit labeled with value | | `continue` | `loop`, `while...do`, `for...do`, `for...yield` | Next iteration | | `continue value` | `for...yield` | Substitute value | | `continue:label` | Labeled `loop`, `while`, `for` | Continue labeled loop | | `continue:label value` | Labeled `for...yield` | Substitute in labeled yield | The following uses are compile-time errors: - `break` or `continue` outside any loop: error - `break value` in `for...do` or `while...do`: error (E0860) — these forms have type `void` - `continue value` in `loop` or `while`: error (E0861) — these loops do not accumulate values - `continue:label value` targeting a `for...do`: error (E0873) - `continue:label` targeting a labeled `block`: error — blocks do not iterate - Reference to undefined label: error - Label shadowing: error (E0871) --- --- title: "Errors and Panics" description: "Clause 17: Ori Language Specification — Errors and Panics" order: 17 section: "Language" --- # 17 Errors and panics Ori distinguishes between recoverable errors and unrecoverable panics. > **Grammar:** See [grammar.ebnf](grammar.md) § PATTERNS (catch_expr) ## 17.1 Recoverable errors Recoverable errors use `Result` and `Option` types. See [Types](08-types.md) for type definitions and methods. ### 17.1.1 Error propagation The `?` operator propagates errors. See [Control Flow](16-control-flow.md) for details. ```ori @load (path: str) -> Result = { let content = read_file(path)?; let data = parse(content)?; Ok(data) } ``` ## 17.2 Panics A _panic_ is an unrecoverable error that terminates normal execution. ### 17.2.1 Implicit panics The following operations cause implicit panics: | Operation | Condition | Panic message | |-----------|-----------|---------------| | List index | Index out of bounds | "index out of bounds: index N, length M" | | String index | Index out of bounds | "index out of bounds: index N, length M" | | `.unwrap()` | Called on `None` | "called unwrap on None" | | `.unwrap()` | Called on `Err(e)` | "called unwrap on Err: {e}" | | Division | Divisor is zero | "division by zero" | | Modulo | Divisor is zero | "modulo by zero" | | Integer arithmetic | Result overflows `int` range | "integer overflow in {operation}" | | Division/modulo | `int.min / -1` or `int.min % -1` | "integer overflow in {operation}" | ### 17.2.2 Explicit panic The `panic` function triggers a panic explicitly: ```ori panic(message) ``` `panic` has return type `Never` and never returns normally: ```ori let x: int = if valid then value else panic("invalid state"); ``` ### 17.2.3 Panic behavior When a panic occurs: 1. Error message is recorded with source location 2. Stack trace is captured 3. If inside `catch(...)`, control transfers to the catch 4. Otherwise, message and trace print to stderr, program exits with code 1 ### 17.2.4 Panic message format Panic messages include the source location where the panic occurred: ``` at :: ``` For example: ```ori panic(msg: "invalid state") // Produces: "invalid state at src/main.ori:42:5" ``` This format applies to both explicit `panic()` calls and implicit panics (index out of bounds, division by zero, etc.). ### 17.2.5 PanicInfo type The `PanicInfo` type contains structured information about a panic: ```ori type PanicInfo = { message: str, location: TraceEntry, stack_trace: [TraceEntry], thread_id: Option, } ``` | Field | Description | |-------|-------------| | `message` | The panic message string | | `location` | Source location where the panic occurred | | `stack_trace` | Full call stack at panic time | | `thread_id` | Task identifier if in concurrent context | `PanicInfo` is available in the prelude. It implements `Printable` and `Debug`: ```ori impl PanicInfo: Printable { @to_str (self) -> str = `panic at {self.location.file}:{self.location.line}:{self.location.column}: {self.message}`; } ``` NOTE The `catch` pattern returns `Result`, not `Result`. `PanicInfo` is available for custom panic handlers via the optional `@panic` entry point. ## 17.3 Integer overflow Integer arithmetic panics on overflow: ```ori let max: int = 9223372036854775807; // max signed 64-bit let result = catch(expr: max + 1); // Err("integer overflow") ``` Addition, subtraction, multiplication, and negation all panic on overflow. Programs requiring wrapping or saturating arithmetic should use methods from `std.math`. ## 17.4 Catching panics The `catch` pattern captures panics and converts them to `Result`: ```ori let result = catch(expr: dangerous_operation()); // result: Result match result { Ok(value) -> use(value), Err(msg) -> handle_error(msg), } ``` If the expression evaluates successfully, `catch` returns `Ok(value)`. If the expression panics, `catch` returns `Err(message)` where `message` is the panic message string. ### 17.4.1 Nested catch `catch` expressions may be nested. A panic propagates to the innermost enclosing `catch`: ```ori catch(expr: { let x = catch(expr: may_panic())?, // inner catch process(x), // may also panic }) // outer catch handles panics from process() ``` ### 17.4.2 Limitations `catch` cannot recover from: - Process-level signals (SIGKILL, SIGSEGV) - Out of memory conditions - Stack overflow These conditions terminate the program immediately. ## 17.5 Panic assertions The prelude provides two functions for testing panic behavior: ### 17.5.1 `assert_panics` ```ori assert_panics(f: () -> void) -> void ``` `assert_panics` evaluates the thunk `f`. If `f` panics, the assertion succeeds. If `f` returns normally, `assert_panics` itself panics with the message `"assertion failed: expected panic but succeeded"`. ### 17.5.2 `assert_panics_with` ```ori assert_panics_with(f: () -> void, msg: str) -> void ``` `assert_panics_with` evaluates the thunk `f`. If `f` panics with a message equal to `msg`, the assertion succeeds. If `f` panics with a different message or returns normally, `assert_panics_with` panics. ## 17.6 Error conventions The `Error` trait provides a standard interface for error types: ```ori trait Error { @message (self) -> str; } ``` Custom error types should implement `Error`: ```ori type ParseError = { line: int, message: str } impl ParseError: Error { @message (self) -> str = "line " + str(self.line) + ": " + self.message; } ``` Functions returning `Result` conventionally use `E: Error`, but any type may be used as the error type. ## 17.7 Error return traces When the `?` operator propagates an error, the source location is automatically recorded. This builds an _error return trace_ showing the propagation path. ### 17.7.1 Automatic collection ```ori @load (path: str) -> Result = try { let content = read_file(path)?, // location recorded if Err let parsed = parse(content)?, // location recorded if Err Ok(parsed) } ``` Traces are collected unconditionally in all builds. No syntax changes required. ### 17.7.2 TraceEntry type ```ori type TraceEntry = { function: str, file: str, line: int, column: int, } ``` The `function` field includes the `@` prefix for function names (e.g., `"@load_config"`). ### 17.7.3 Trace ordering Entries are ordered most recent first (like a stack trace). The first entry is the most recent `?` propagation point. ### 17.7.4 Accessing traces The `Error` type provides trace access methods: | Method | Return Type | Description | |--------|-------------|-------------| | `.trace()` | `str` | Formatted trace string | | `.trace_entries()` | `[TraceEntry]` | Programmatic access | | `.has_trace()` | `bool` | Check if trace available | ### 17.7.5 Trace format The `.trace()` method returns: ``` at :: ``` One entry per line, most recent propagation point first. Function names are left-padded to align the "at" column. ### 17.7.6 Context method `Result` provides a `.context()` method to add context while preserving traces: ```ori @load_config () -> Result = try { let content = read_file("config.json") .context("failed to load config")?; Ok(parse(content)) } ``` ### 17.7.7 Traceable trait Custom error types may implement `Traceable` to carry their own traces: ```ori trait Traceable { @with_trace (self, trace: [TraceEntry]) -> Self; @trace (self) -> [TraceEntry]; } ``` `Traceable` is optional. For non-implementing error types, traces attach to the `Result` wrapper during propagation. ### 17.7.8 Result trace methods `Result` provides trace access regardless of whether `E` implements `Traceable`: | Method | Return Type | Description | |--------|-------------|-------------| | `.trace()` | `str` | Formatted trace string | | `.trace_entries()` | `[TraceEntry]` | Programmatic access | | `.has_trace()` | `bool` | Check if trace available | When `E: Traceable`, these methods delegate to the error's trace methods. When `E` does not implement `Traceable`, the `Result` carries the trace internally. ### 17.7.9 Context storage When `.context(msg:)` is called on a `Result`, the context string is stored separately from the trace. Contexts are ordered most recent first, matching trace ordering. For error types implementing `Traceable`, contexts are stored in the error value. For non-Traceable errors, contexts are stored in the `Result` wrapper alongside the trace. ### 17.7.10 Relationship to panic traces | Aspect | Error Return Trace | Panic Stack Trace | |--------|-------------------|-------------------| | Trigger | `?` propagation | `panic()` or implicit panic | | Contents | Only `?` propagation points | Full call stack | | Recovery | Via `Result` handling | Via `catch(...)` | The two trace types may intersect. If an error is converted to a panic (e.g., via `.unwrap()`), the panic trace includes the unwrap location, while the error's return trace shows how the error arrived there. ## 17.8 Async error traces Error traces are preserved across task boundaries in concurrent code. ### 17.8.1 Task boundary marker When an error crosses from a spawned task to the parent task, a marker entry is inserted into the trace: ```ori TraceEntry { function: "", file: "", line: 0, column: 0 } ``` This pseudo-entry indicates where the error crossed task boundaries, helping distinguish between propagation within a task and propagation across tasks. ### 17.8.2 Trace from parallel tasks Each task in `parallel(...)` or `nursery(...)` maintains its own trace. When errors are collected: ```ori @process_all (items: [int]) -> [Result] uses Suspend = parallel(tasks: items.map(i -> () -> process(i))); // Each result's trace shows: // - Propagation points within the spawned task // - Task boundary marker // - Propagation points in the parent task (if any) ``` ### 17.8.3 Catch and panic traces The `catch` pattern converts panics to `Result`. Panics do not generate structured `?`-style traces because they bypass normal return flow. The panic message string contains the location information but not a `[TraceEntry]` list. If code within `catch` returns `Err` (not a panic), the error's trace is preserved normally: ```ori let result = catch(expr: { let x = fallible()?, // Trace entry added Ok(x) }); // result: Result, str> // Inner Err has trace; outer Ok means no panic ``` --- --- title: "Modules" description: "Clause 18: Ori Language Specification — Modules" order: 18 section: "Language" --- # 18 Modules Every source file defines one module. > **Grammar:** See [grammar.ebnf](grammar.md) § SOURCE STRUCTURE (import, extension_def, extension_import) ## 18.1 Entry point files | File | Purpose | |------|---------| | `main.ori` | Binary entry point (shall contain `@main`) | | `lib.ori` | Library entry point (defines public API) | | `mod.ori` | Directory module entry point (within a package) | `lib.ori` is the package-level public interface. `mod.ori` is a directory-level public interface within a package. A package root cannot use `mod.ori` as its library entry point; `lib.ori` is required. ## 18.2 Module names | File Path | Module Name | |-----------|-------------| | `src/main.ori` | `main` | | `src/lib.ori` | (package name) | | `src/math.ori` | `math` | | `src/http/client.ori` | `http.client` | | `src/http/mod.ori` | `http` | ## 18.3 Imports ### 18.3.1 Relative (local files) ```ori use "./math" { add, subtract }; use "../utils/helpers" { format }; ``` Paths start with `./` or `../`, resolve from current file, omit `.ori`. ### 18.3.2 Module (stdlib/packages) ```ori use std.math { sqrt, abs }; use std.net.http as http; ``` ### 18.3.3 Private access ```ori use "./math" { ::internal_helper }; ``` `::` prefix imports private (non-pub) items. ### 18.3.4 Aliases ```ori use "./math" { add as plus }; use std.collections { HashMap as Map }; ``` ### 18.3.5 Default bindings When importing a trait that has a `def impl` in its source module, the default implementation is automatically bound to the trait name: ```ori use std.net.http { Http }; // Http bound to default impl Http.get(url: "...") // Uses default ``` Override with `with...in`: ```ori with Http = MockHttp {} in Http.get(url: "...") // Uses mock ``` To import a trait without its default: ```ori use std.net.http { Http without def }; // Import trait, skip def impl ``` See [Declarations § Default Implementations](10-declarations.md#105-default-implementations). ## 18.4 Visibility Items are private by default. `pub` exports: ```ori pub @add (a: int, b: int) -> int = a + b; pub type User = { id: int, name: str } pub $timeout = 30s; ``` ### 18.4.1 Nested module visibility Parent modules cannot access child private items. Child modules cannot access parent private items. Sibling modules cannot access each other's private items. The `::` prefix allows any module to explicitly import private items from another module: ```ori use "./internal" { ::private_helper }; // Explicit private access ``` The `::` prefix makes the boundary crossing visible at the import site. The compiler does not restrict where `::` imports may appear. ## 18.5 Re-exports ```ori pub use "./client" { get, post }; ``` Re-exporting a trait includes its `def impl` if both are public: ```ori pub use std.logging { Logger }; // Re-exports trait AND def impl ``` To re-export a trait without its default: ```ori pub use std.logging { Logger without def }; // Strips def impl permanently ``` When a trait is re-exported `without def`, consumers cannot access the original default through that export path — they shall import from the original source. ### 18.5.1 Re-export chains Re-exports can chain through multiple levels. An item shall be `pub` at every level of the chain: ```ori // level3.ori pub @deep () -> str = "deep"; // level2.ori pub use "./level3" { deep }; // level1.ori pub use "./level2" { deep }; // main.ori use "./level1" { deep }; // Works through the chain ``` Aliases propagate through chains. The same underlying item imported through multiple paths is not an error. ## 18.6 Extensions Extensions add methods to existing types without modifying their definition. ### 18.6.1 Definition ```ori extend Iterator { @count (self) -> int = ...; } ``` Constrained extensions use angle brackets or where clauses (equivalent): ```ori // Angle bracket form extend [T] { @duplicate_all (self) -> [T] = self.map(transform: x -> x.clone()); } // Where clause form extend [T] where T: Clone { @duplicate_all (self) -> [T] = self.map(transform: x -> x.clone()); } extend Iterator where Self.Item: Add { @sum (self) -> Self.Item = ...; } ``` Extensions may be defined for concrete types, generic types, trait implementors, and constrained generics. Extensions cannot: - Add fields to types - Implement traits (use `impl Type: Trait`) - Override existing methods - Add static methods (methods without `self`) ### 18.6.2 Visibility Visibility is block-level. All methods in a `pub extend` block are public; all in a non-pub block are module-private. ```ori pub extend Iterator { @count (self) -> int = ...; // Publicly importable } extend Iterator { @internal (self) -> int = ...; // Module-private } ``` ### 18.6.3 Import ```ori extension std.iter.extensions { Iterator.count, Iterator.last }; extension "./my_ext" { Iterator.sum }; ``` Method-level granularity required; wildcards prohibited. ### 18.6.4 Resolution order When calling `value.method()`: 1. **Inherent methods** — methods in `impl Type { }` 2. **Trait methods** — methods from implemented traits 3. **Extension methods** — methods from imported extensions If multiple imported extensions define the same method, the call is ambiguous. Use qualified syntax: ```ori use "./ext_a" as ext_a; ext_a.Point.distance(p) // Calls ext_a's implementation ``` ### 18.6.5 Scoping Extension imports are file-scoped. To re-export: ```ori pub extension std.iter.extensions { Iterator.count }; ``` ### 18.6.6 Orphan rules An extension shall be in the same package as either the type being extended OR at least one trait bound in a constrained extension. ## 18.7 Resolution 1. Local bindings (inner first) 2. Function parameters 3. Module-level items 4. Imports 5. Prelude Circular dependencies prohibited. The compiler detects cycles using depth-first traversal of the import graph and reports all cycles found. ## 18.8 Import path resolution When processing a `use` statement, the compiler determines the target module: 1. **Relative path** (`"./..."`, `"../..."`): Resolve relative to current file's directory 2. **Package path** (`"pkg_name"`): Look up in `ori.toml` dependencies 3. **Standard library** (`std.xxx`): Built-in stdlib modules This is distinct from *name resolution* within a module (see Resolution above). ## 18.9 Prelude Available without import: **Types**: `int`, `float`, `bool`, `str`, `char`, `byte`, `void`, `Never`, `Duration`, `Size`, `Option`, `Result`, `Ordering`, `Error`, `TraceEntry`, `Range`, `Set`, `Channel`, `[T]`, `{K: V}`, `FormatSpec`, `Alignment`, `Sign`, `FormatType` **Functions**: `print`, `len`, `is_empty`, `is_some`, `is_none`, `is_ok`, `is_err`, `int`, `float`, `str`, `byte`, `compare`, `min`, `max`, `panic`, `todo`, `unreachable`, `dbg`, `hash_combine`, all assertions **Traits**: `Eq`, `Comparable`, `Hashable`, `Printable`, `Formattable`, `Debug`, `Clone`, `Default`, `Iterator`, `DoubleEndedIterator`, `Iterable`, `Collect`, `Into`, `Traceable` | Trait | Method | Description | |-------|--------|-------------| | `Eq` | `==`, `!=` | Equality comparison | | `Comparable` | `.compare()` | Ordering comparison | | `Hashable` | `.hash()` | Hash value for map keys | | `Printable` | `.to_str()` | String representation | | `Formattable` | `.format()` | Formatted string with spec | | `Debug` | `.debug()` | Developer-facing representation | | `Clone` | `.clone()` | Explicit value duplication | | `Default` | `.default()` | Default value construction | | `Iterator` | `.next()` | Iterate forward | | `DoubleEndedIterator` | `.next_back()` | Iterate both directions | | `Iterable` | `.iter()` | Produce an iterator | | `Collect` | `.from_iter()` | Build from iterator | | `Into` | `.into()` | Type conversion | | `Traceable` | `.with_trace()`, `.trace()` | Error trace propagation | **Functions**: `repeat` ## 18.10 Standard library | Module | Description | |--------|-------------| | `std.math` | Mathematical functions | | `std.io` | I/O traits | | `std.fs` | Filesystem | | `std.net.http` | HTTP | | `std.time` | Date/time | | `std.json` | JSON | | `std.crypto` | Cryptography | ## 18.11 Test modules Tests in `_test/` with `.test.ori` extension can access private items: ``` src/ math.ori _test/ math.test.ori ``` ## 18.12 Package structure ### 18.12.1 Library package A library package exports its public API via `lib.ori`: ``` my_lib/ ├── ori.toml ├── src/ │ ├── lib.ori # Library entry point │ └── internal.ori # Internal implementation ``` ### 18.12.2 Binary package A binary package has `main.ori` with an `@main` function: ``` my_app/ ├── ori.toml ├── src/ │ ├── main.ori # Binary entry point │ └── utils.ori ``` ### 18.12.3 Library + binary A package can contain both. The binary imports from the library using the package name and can only access public items: ```ori // lib.ori pub @exported () -> int = 42; @internal () -> int = 1; // Private // main.ori use "my_pkg" { exported }; // OK: public use "my_pkg" { ::internal }; // ERROR: private access not allowed ``` This enforces clean API boundaries. ## 18.13 Package manifest ```toml [project] name = "my_project" version = "0.1.0" [dependencies] some_lib = "1.0.0" ``` Entry point: `@main` function for binaries, `lib.ori` for libraries. --- --- title: "Testing" description: "Clause 19: Ori Language Specification — Testing" order: 19 section: "Language" --- # 19 Testing Tests are first-class constructs bound to their targets via the `tests` keyword. The compiler tracks test coverage and executes affected tests automatically during compilation. Test enforcement is configurable per project (see §19.2). > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (test, attribute) > > **Implementation Model:** See [Test Execution Model Proposal](../../proposals/approved/test-execution-model-proposal.md) for data structures, algorithms, and cache formats. ## 19.1 Test declaration A _test_ is a function that verifies the behavior of one or more target functions. All tests shall use the `tests` keyword. > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (test) ### 19.1.1 Attached tests An _attached test_ declares one or more functions it tests: ```ori @test_add tests @add () -> void = { assert_eq(actual: add(a: 2, b: 3), expected: 5); } ``` Multiple targets are specified by repeating the `tests` keyword: ```ori @test_roundtrip tests @parse tests @format () -> void = { let ast = parse(input: "x + 1"); let output = format(ast: ast); assert_eq(actual: output, expected: "x + 1"); } ``` An attached test satisfies the test coverage requirement for all of its targets. ### 19.1.2 Floating tests A _floating test_ uses `_` as its target, indicating it tests no specific function: ```ori @test_integration tests _ () -> void = { let result = full_pipeline(input: "program"); assert_ok(result: result); } ``` Floating tests: - Do not satisfy coverage requirements for any function - Do not run during normal compilation - Run only via explicit `ori test` command The `_` token is consistent with its use elsewhere in the language: pattern matching wildcards, ignored lambda parameters. ### 19.1.3 Test signature All tests shall: - Take no parameters: `()` - Return `void`: `-> void` - Have a body expression ```ori // Valid @test_example tests @example () -> void = {...} // Invalid - tests cannot have parameters @test_bad tests @bad (x: int) -> void = ...; // error // Invalid - tests must return void @test_bad tests @bad () -> int = ...; // error ``` ## 19.2 Test coverage enforcement A conforming implementation shall support test enforcement at three levels: | Level | Behavior | |-------|----------| | `"off"` | No enforcement. Missing tests produce no diagnostic. | | `"warn"` | Missing tests produce a warning (E3010). | | `"error"` | Missing tests produce a compile-time error (E3010). | The default enforcement level shall be `"off"`. NOTE The enforcement level can be set via the `--test-enforcement` command-line flag or the `test-enforcement` key in project configuration. In single-file mode (no project configuration), enforcement shall be `"off"`. When enforcement is `"warn"` or `"error"`, the compiler shall report a diagnostic for each function declaration that has no attached test: ``` error[E3010]: function `@multiply` has no tests --> src/math.ori:15:1 | 15 | @multiply (a: int, b: int) -> int = a * b | ^^^^^^^^^ missing test | = help: add a test with `@test_multiply tests @multiply () -> void = ...` ``` ### 19.2.1 Exemptions The following declarations are exempt from the test coverage requirement regardless of enforcement level: - `@main` — program entry point - Test functions — tests do not require tests - Immutable bindings (`let $name = ...`) — constants - Type definitions (`type Name = ...`) - Trait definitions (`trait Name { ... }`) - Trait implementations (`impl Type: Trait { ... }`) - Default implementations (`def impl Trait { ... }`) ## 19.3 Test execution model Tests execute as part of the compilation process. The compiler integrates test execution after successful type checking of affected code. ### 19.3.1 Compilation phases ``` Source Files │ ▼ ┌─────────────────┐ │ Parse │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Type Check │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Test Discovery │ ◄── Identify tests for affected functions └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Test Execution │ ◄── Run affected attached tests └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Code Gen │ (if requested) └─────────────────┘ ``` Tests run after type checking succeeds for their targets. A test cannot execute if its target function fails to type check. ### 19.3.2 Execution guarantees - Tests execute in isolation with no shared mutable state - Tests may execute in parallel when they have no ordering dependencies - Each test receives a fresh environment - Test execution order is unspecified ## 19.4 Dependency-aware execution The compiler maintains a _dependency graph_ tracking which functions call which other functions. Test execution uses this graph to determine which tests to run when code changes. ### 19.4.1 Forward and reverse dependencies For any function `f`: - _Forward dependencies_: functions that `f` calls - _Reverse dependencies_ (callers): functions that call `f` ``` @helper ← @process ← @handle_request ← @main │ │ │ │ │ └── reverse dependency of @process │ └── reverse dependency of @helper └── forward dependency of @process ``` ### 19.4.2 Reverse transitive closure When a function changes, the compiler computes its _reverse transitive closure_: the set of all functions that directly or transitively depend on it. Given this dependency graph: ``` @parse ← @compile ← @run_program ↑ @optimize ``` If `@parse` changes: - Direct reverse dependencies: `@compile` - Transitive reverse dependencies: `@run_program` - Reverse transitive closure: `{@parse, @compile, @run_program}` ### 19.4.3 Affected test determination A test is _affected_ by a change if any function in the reverse transitive closure is one of its targets. ```ori @test_parse tests @parse () -> void = ...; @test_compile tests @compile () -> void = ...; @test_optimize tests @optimize () -> void = ...; @test_run tests @run_program () -> void = ...; ``` If `@parse` changes: - `@test_parse` runs (direct target) - `@test_compile` runs (`@compile` calls `@parse`) - `@test_run` runs (`@run_program` calls `@compile` which calls `@parse`) - `@test_optimize` does not run (`@optimize` does not depend on `@parse`) ### 19.4.4 Algorithm ``` function affected_tests(changed_functions): affected = {} for func in changed_functions: affected.add(func) affected.union(reverse_transitive_closure(func)) return tests where any target in affected function reverse_transitive_closure(func): result = {func} queue = [func] while queue is not empty: current = queue.pop() for caller in direct_callers(current): if caller not in result: result.add(caller) queue.append(caller) return result ``` ## 19.5 Incremental compilation During incremental compilation, the compiler tracks which functions have changed and executes only the tests affected by those changes. ### 19.5.1 Change detection A function is considered _changed_ if: - Its source code has been modified (detected via content hash) - Any of its forward dependencies has changed (transitive) The compiler maintains a cache of function content hashes: ``` .ori/cache/ ├── hashes.bin # Function content hashes ├── deps.bin # Dependency graph └── test-results/ # Cached test results ``` ### 19.5.2 Incremental execution flow 1. **Detect changes**: Compare current function hashes to cached hashes 2. **Compute affected set**: Build reverse transitive closure of changed functions 3. **Filter tests**: Select attached tests where any target is in affected set 4. **Check cache**: Skip tests whose inputs (target hashes) match cached results 5. **Execute**: Run tests not satisfied by cache 6. **Update cache**: Store new results keyed by input hashes ### 19.5.3 Full compilation During full compilation (no cache or cache invalidated): 1. All attached tests execute 2. Results are cached for subsequent incremental builds 3. Floating tests do not execute (require explicit `ori test`) ## 19.6 Test results ### 19.6.1 Non-blocking execution By default, test failures are reported but do not block compilation: ``` $ ori check src/math.ori Compiling... ✓ @add (changed) Running affected tests... ✗ @test_add assertion failed: expected 5, got 6 at src/math.ori:12:5 Build succeeded with 1 test failure. ``` The compilation completes, allowing developers to iterate on failing tests. ### 19.6.2 Strict mode In strict mode (`--strict`), any test failure causes the build to fail: ``` $ ori check --strict src/math.ori Compiling... Running affected tests... ✗ @test_add Build FAILED: 1 test failure. ``` Strict mode is intended for CI environments and pre-commit hooks. ### 19.6.3 Result states A test execution produces one of the following results: | Result | Meaning | |--------|---------| | Pass | All assertions succeeded | | Fail | An assertion failed or the test panicked | | Skip | Test has `#skip` attribute | | Error | Test could not execute (e.g., target failed to compile) | ## 19.7 Performance considerations Attached tests run during compilation and should be fast. The compiler emits a warning if an attached test exceeds the slow test threshold. ### 19.7.1 Slow test warning ``` warning: attached test @test_parse took 250ms --> src/parser.ori:45:1 | 45 | @test_parse tests @parse () -> void = ... | ^^^^^^^^^^^ slow attached test | = note: attached tests run during compilation = help: consider making this a floating test: `tests _` = note: threshold is 100ms (configurable in ori.toml) ``` ### 19.7.2 Threshold configuration The slow test threshold is configurable via `ori.toml`: ```toml [testing] slow_test_threshold = "100ms" ``` Supported duration units: `ms`, `s`, `m`. Default is `100ms`. ### 19.7.3 Guidelines - Attached tests should complete in under 100ms - Use capability mocking to avoid I/O in attached tests - Use floating tests (`tests _`) for integration tests requiring real I/O - Use floating tests for tests with complex setup or large data sets ## 19.8 Test attributes ### 19.8.1 skip A skipped test is parsed and type-checked but not executed: ```ori #skip("waiting for feature X") @test_feature tests @feature () -> void = {...} ``` Skipped tests satisfy the coverage requirement for their targets. ### 19.8.2 compile_fail A compile-fail test passes if compilation fails with an error containing the specified substring: ```ori #compile_fail("type mismatch") @test_type_error tests @main () -> void = { let x: int = "hello"; () } ``` The test fails if: - Compilation succeeds - Compilation fails but error message does not contain the substring ### 19.8.3 fail A fail test passes if execution panics with a message containing the specified substring: ```ori #fail("division by zero") @test_div_zero tests @divide () -> void = { divide(a: 10, b: 0); () } ``` The test fails if: - Execution completes without panicking - Execution panics but message does not contain the substring ## 19.9 Assertions The following assertion functions are available in the prelude: ``` assert(condition: bool) -> void assert_eq(actual: T, expected: T) -> void assert_ne(actual: T, unexpected: T) -> void assert_some(opt: Option) -> void assert_none(opt: Option) -> void assert_ok(result: Result) -> void assert_err(result: Result) -> void assert_panics(f: () -> void) -> void assert_panics_with(f: () -> void, msg: str) -> void ``` All assertions panic on failure with a descriptive message including the source location. ## 19.10 Test organization All tests shall be placed in a `_test/` subdirectory with `.test.ori` suffix. It is a compile-time error to define a test function outside of a `_test/` directory. ``` src/ ├── math.ori └── _test/ └── math.test.ori ``` ``` error: test defined outside _test/ directory --> src/math.ori:5:1 | 5 | @test_add tests @add () -> void = ... | ^^^^^^^^^ tests must be in a _test/ directory | = help: move this test to src/_test/math.test.ori ``` NOTE The error code for this diagnostic is reserved but not yet assigned. The `_test/` directory convention is not yet enforced by the compiler. This convention cleanly separates test code from production code. Test files are excluded from compiled output by directory path alone — no conditional compilation flags or build-time stripping required. ### 19.10.1 Test file naming Test files use the `.test.ori` suffix. By convention, each source file `foo.ori` has a corresponding `_test/foo.test.ori`, though a single test file may test functions from multiple source files. ### 19.10.2 Example ```ori // src/_test/math.test.ori use "../math" { add, ::internal_helper }; @test_add tests @add () -> void = { assert_eq(actual: add(a: 2, b: 3), expected: 5); } @test_helper tests @internal_helper () -> void = { assert_eq(actual: internal_helper(x: 5), expected: 10); } ``` Private items may be imported using the `::` prefix (see [Modules § Private Access](18-modules.md#1833-private-access)). ## 19.11 Testing capabilities Functions with capabilities are tested by providing mock implementations via `with...in`: ```ori @fetch_user (id: int) -> Result uses Http = { let response = Http.get(url: `/users/{id}`)?; Ok(parse_user(data: response)) } @test_fetch_user tests @fetch_user () -> void = with Http = MockHttp { responses: {"/users/1": `{"name": "Alice"}`} } in { let result = fetch_user(id: 1); assert_ok(result: result); let user = result.unwrap(); assert_eq(actual: user.name, expected: "Alice"); } ``` This enables fast, deterministic tests without actual I/O. ## 19.12 Command-line interface ### 19.12.1 ori check Compiles source files and runs affected attached tests: ``` ori check [OPTIONS] Options: --test-enforcement=off|warn|error Set test coverage enforcement level (default: off) --no-test Compile only, skip test execution --strict Fail build on any test failure --verbose Show all test results, not just failures ``` ### 19.12.2 ori test Runs all tests (attached and floating): ``` ori test [OPTIONS] [PATH] Options: --only-attached Run only attached tests (skip floating) --filter Run only tests matching pattern --verbose Show all test results ``` ### 19.12.3 Execution summary | Command | Targeted Tests | Free-Floating Tests | |---------|----------------|---------------------| | `ori check` | Affected only | Never | | `ori check --no-test` | Never | Never | | `ori test` | All | All | | `ori test --only-attached` | All | Never | --- --- title: "Capabilities" description: "Clause 20: Ori Language Specification — Capabilities" order: 20 section: "Language" --- # 20 Capabilities Capabilities are traits representing effects or suspension. > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (uses_clause), EXPRESSIONS (with_expr) ## 20.1 Declaration ```ori @fetch (url: str) -> Result uses Http = Http.get(url); @save (data: str) -> Result uses FileSystem, Suspend = FileSystem.write(path: "/data.txt", content: data); ``` ## 20.2 Capability traits Capabilities are traits with default implementations: ```ori pub trait Http { @get (url: str) -> Result; @post (url: str, body: str) -> Result; } pub def impl Http { @get (url: str) -> Result = ...; @post (url: str, body: str) -> Result = ...; } ``` Import the trait to use the default: ```ori use std.net.http { Http }; @fetch () -> Result uses Http = Http.get(url: "https://api.example.com/data"); ``` Other standard capability traits: ```ori trait FileSystem { @read (path: str) -> Result; @write (path: str, content: str) -> Result; } trait Print { @print (msg: str) -> void; @println (msg: str) -> void; @output () -> str; @clear () -> void; } ``` ## 20.3 Suspend capability `Suspend` is a marker capability indicating a function may suspend. A function with `uses Suspend` requires a suspending context to execute. | With `uses Suspend` | Without | |---------------------|---------| | Non-blocking, may suspend | Blocking, synchronous | ```ori @fetch_suspending (url: str) -> Result uses Http, Suspend = ...; // may suspend @fetch_sync (url: str) -> Result uses Http = ...; // blocks ``` No `async` type modifier. No `.await` expression. Return type is final value type. Concurrency via `parallel` pattern: ```ori parallel(tasks: [fetch(a), fetch(b)], max_concurrent: 10) ``` See [Concurrency Model](22-concurrency-model.md) for task definitions, suspending context semantics, and suspension points. ## 20.4 Providing capabilities Default implementations are automatically bound when importing a trait. Override with `with...in` when custom configuration or mocking is needed: ```ori with Http = ConfiguredHttp { timeout: 5s } in fetch("/data") ``` ### 20.4.1 Multi-binding syntax Multiple capabilities may be bound in a single `with` expression using comma-separated bindings: ```ori with Http = mock_http, Cache = mock_cache in complex_operation() ``` This is equivalent to nested `with` expressions: ```ori with Http = mock_http in with Cache = mock_cache in complex_operation() ``` ### 20.4.2 Partial provision When a function requires multiple capabilities, some may be explicitly provided while others use defaults: ```ori def impl Http { ... } def impl Cache { ... } def impl Logger { ... } @test_with_mock_http () -> void = { let mock = MockHttp { ... }; with Http = mock in complex_operation(), // MockHttp + default Cache + default Logger } ``` Only `Http` is overridden; `Cache` and `Logger` use their `def impl`. ### 20.4.3 Nested binding semantics Inner bindings shadow outer bindings within their scope: ```ori with Http = OuterHttp in { use_http(), // OuterHttp with Http = InnerHttp in use_http(), // InnerHttp (shadows Outer) use_http(), // OuterHttp again } ``` `with` creates a lexical scope — bindings are visible only within: ```ori let result = with Http = mock in fetch(); // mock is NOT bound here fetch() // Uses default Http, not mock ``` ## 20.5 Stateful handlers > **Grammar:** See [grammar.ebnf](grammar.md) § EXPRESSIONS (handler_expr) > > **Proposal:** [stateful-mock-testing-proposal.md](../../proposals/approved/stateful-mock-testing-proposal.md) A _stateful handler_ is a `with...in` binding that threads local mutable state through handler operations. The `handler(state: expr) { ... }` construct creates a handler frame with frame-local state, enabling stateful capability mocking while preserving value semantics. `handler` is a context-sensitive keyword, valid only in the expression position of a `capability_binding`. ### 20.5.1 Syntax ```ori with Counter = handler(state: 0) { increment: (s) -> (s + 1, s + 1), get: (s) -> (s, s), } in { let a = Counter.increment(), // state: 0 -> 1, returns 1 let b = Counter.increment(), // state: 1 -> 2, returns 2 a + b, // 3 } ``` ### 20.5.2 Semantics 1. The `state:` initializer determines the initial state value and its type `S` 2. Each handler operation receives the current state as its first argument, replacing `self` 3. Each handler operation shall return `(S, R)` where `S` is the next state and `R` is the trait method's return type 4. State is threaded through operations sequentially within the `with...in` scope 5. The `with...in` expression returns the body's type; handler state is internal ### 20.5.3 Type checking rules For a handler operation named `op` implementing trait method `@op (self, p1: T1, ..., pN: TN) -> R`: - The handler operation receives `(state: S, p1: T1, ..., pN: TN)` - The handler operation shall return `(S, R)` - The state type `S` is inferred from the `state:` initializer - All handler operations shall use the same state type - Every required trait method shall have a corresponding handler operation; default trait methods are used if not overridden - Handler operations for non-existent trait methods are an error A stateful handler is not a type and has no `self`. It satisfies the trait's interface for the duration of the `with...in` scope through a distinct dispatch mechanism from `impl` blocks. ### 20.5.4 State composition Handlers support a single state value. Multiple independent state values are composed into a struct or tuple: ```ori with Counter = handler(state: { count: 0, log: [] }) { increment: (s) -> ({ ...s, count: s.count + 1, log: [...s.log, "inc"] }, s.count + 1), get: (s) -> (s, s.count), } in ... ``` ### 20.5.5 Nested handlers Each handler maintains independent state. Cross-handler calls dispatch through normal capability resolution: ```ori with Logger = handler(state: []) { log: (s, msg: str) -> ([...s, msg], ()), } in with Counter = handler(state: 0) { increment: (s) -> { Logger.log(msg: "increment"), // invokes outer handler (s + 1, s + 1) }, } in ... ``` ### 20.5.6 Restrictions - `def impl` cannot be stateful (stateless by design, no scope for state lifetime) - Stateful handlers are available in all `with...in` scopes (not restricted to test code) ## 20.6 Capability variance A context with more capabilities may call functions requiring fewer: ```ori @needs_http () -> void uses Http = ...; @needs_both () -> void uses Http, Cache = ...; @caller () -> void uses Http, Cache = { needs_http(), // OK: caller has Http needs_both(), // OK: caller has both } ``` A function requiring more capabilities cannot be called from one with fewer: ```ori @needs_both () -> void uses Http, Cache = ...; @caller () -> void uses Http = { needs_both(), // ERROR: caller lacks Cache } ``` ## 20.7 Propagation Capabilities propagate: if A calls B with capability C, A shall declare or provide C. ```ori @helper () -> str uses Http = Http.get("/").body; // Must declare Http @caller () -> str uses Http = helper(); // Or provide it @caller () -> str = with Http = MockHttp {} in helper(); ``` ## 20.8 Standard capabilities | Capability | Purpose | May Suspend | |------------|---------|-------------| | `Http` | HTTP client | Yes | | `FileSystem` | File I/O | Yes | | `Cache` | Caching | Yes | | `Clock` | Time | No | | `Random` | RNG | No | | `Crypto` | Cryptographic operations | No | | `Print` | Standard output | No | | `Logger` | Structured logging | No | | `Env` | Environment | No | | `Intrinsics` | Low-level SIMD and bit operations | No | | `FFI` | Foreign function interface | No | | `Suspend` | Suspension marker | Yes | | `Unsafe` | Safety bypass marker | No | ### 20.8.1 Cache capability The `Cache` capability provides key-value caching with TTL-based expiration: ```ori trait Cache { @get (self, key: K) -> Option; @set (self, key: K, value: V, ttl: Duration) -> void; @invalidate (self, key: K) -> void; @clear (self) -> void; } ``` Used by the `cache` pattern (see [Patterns § cache](15-patterns.md#1551-cache)). Cache implementations differ in suspension behavior: | Implementation | Description | Suspends | |----------------|-------------|----------| | `InMemoryCache` | Process-local | No | | `DistributedCache` | Shared across nodes | Yes | | `NoOpCache` | Disable caching (always miss) | No | When using a suspending cache implementation, the calling function shall have `uses Suspend` or be called from a suspending context. ### 20.8.2 Clock capability The `Clock` capability provides access to the current time: ```ori trait Clock { @now () -> Instant; @local_timezone () -> Timezone; } ``` `Instant` and `Timezone` are defined in `std.time`. Functions requiring current time shall declare `uses Clock`: ```ori @log_timestamp (msg: str) -> void uses Clock, Print = print(msg: `[{Clock.now()}] {msg}`); ``` Mock clocks enable deterministic testing via _stateful handlers_: ```ori @test_expiry tests @is_expired () -> void = { let start = Instant.from_unix_secs(secs: 1700000000); with Clock = handler(state: start) { now: (s) -> (s, s) advance: (s, by: Duration) -> (s + by, ()) } in { assert(!is_expired(token: token)); Clock.advance(by: 1h); assert(is_expired(token: token)) } } ``` The `handler(state: expr) { ... }` construct creates a stateful handler frame with local mutable state threaded through operations. State is frame-local and does not violate value semantics. See [Stateful Handlers](#205-stateful-handlers) for the full specification. ### 20.8.3 Crypto capability The `Crypto` capability provides cryptographic operations: ```ori trait Crypto { @hash (data: [byte], algorithm: HashAlgorithm) -> [byte]; @hash_password (password: str) -> str; @verify_password (password: str, hash: str) -> bool; @generate_key () -> SecretKey; @encrypt (key: SecretKey, plaintext: [byte]) -> [byte]; @decrypt (key: SecretKey, ciphertext: [byte]) -> Result<[byte], CryptoError>; @random_bytes (count: int) -> [byte]; // ... additional methods defined in std.crypto } ``` Types and functions are defined in `std.crypto`. The `Crypto` capability is non-suspending — cryptographic operations are CPU-bound and complete synchronously. Key types are separated by purpose to prevent misuse at compile time: - `SigningPrivateKey` / `SigningPublicKey` — for digital signatures - `EncryptionPrivateKey` / `EncryptionPublicKey` — for asymmetric encryption - `KeyExchangePrivateKey` / `KeyExchangePublicKey` — for Diffie-Hellman key exchange Private key types (`SecretKey`, `SigningPrivateKey`, `EncryptionPrivateKey`, `KeyExchangePrivateKey`) automatically zero their memory when dropped. ### 20.8.4 Intrinsics capability The `Intrinsics` capability provides generic SIMD operations, bit manipulation, and hardware feature detection. Operations are generic over lane type `T` and width `$N`; the compiler monomorphizes based on the fixed-capacity list type at the call site. ```ori trait Intrinsics { // Generic SIMD operations (T = byte, int, or float; N = lane count) @simd_load (data: [T], offset: int) -> [T, max N]; @simd_load_aligned (data: [T], offset: int) -> [T, max N]; @simd_add (a: [T, max N], b: [T, max N]) -> [T, max N]; @simd_sub (a: [T, max N], b: [T, max N]) -> [T, max N]; @simd_mul (a: [T, max N], b: [T, max N]) -> [T, max N]; @simd_div (a: [T, max N], b: [T, max N]) -> [T, max N]; // float only @simd_sqrt (a: [T, max N]) -> [T, max N]; // float only @simd_abs (a: [T, max N]) -> [T, max N]; // float only @simd_cmpeq (a: [T, max N], b: [T, max N]) -> Mask; @simd_cmplt (a: [T, max N], b: [T, max N]) -> Mask; @simd_cmpgt (a: [T, max N], b: [T, max N]) -> Mask; @simd_min (a: [T, max N], b: [T, max N]) -> [T, max N]; @simd_max (a: [T, max N], b: [T, max N]) -> [T, max N]; @simd_sum (a: [T, max N]) -> T; @simd_splat (value: T) -> [T, max N]; @simd_and (a: [T, max N], b: [T, max N]) -> [T, max N]; // byte, int @simd_or (a: [T, max N], b: [T, max N]) -> [T, max N]; // byte, int @simd_xor (a: [T, max N], b: [T, max N]) -> [T, max N]; // byte, int @simd_andnot (a: [T, max N], b: [T, max N]) -> [T, max N]; // byte, int @simd_select (mask: Mask, a: [T, max N], b: [T, max N]) -> [T, max N]; @simd_shuffle<$N: int> (v: [byte, max N], idx: [byte, max N]) -> [byte, max N]; // byte only // Bit operations @count_ones (value: int) -> int; @count_leading_zeros (value: int) -> int; @count_trailing_zeros (value: int) -> int; @rotate_left (value: int, amount: int) -> int; @rotate_right (value: int, amount: int) -> int; // Hardware queries @cpu_has_feature (feature: str) -> bool; } ``` #### 20.8.4.1 Valid type and width combinations SIMD operations work on fixed-capacity lists representing vector registers. The compiler shall reject invalid `T x N` combinations with error E1063. | Type | Lane width | 128-bit | 256-bit | 512-bit | |------|-----------|---------|---------|---------| | `byte` | 8-bit | `max 16` | `max 32` | `max 64` | | `int` | 64-bit | `max 2` | `max 4` | `max 8` | | `float` | 64-bit | `max 2` | `max 4` | `max 8` | NOTE Ori's `float` type is 64-bit (f64). `[float, max 2]` represents a 128-bit vector of two f64 lanes. #### 20.8.4.2 Mask type Comparison operations return `Mask<$N>` — an opaque type representing N boolean lanes. `Mask<$N>` provides methods for extracting positional information and supports bitwise operators (`&`, `|`, `~`) for combining masks. | Method | Returns | Description | |--------|---------|-------------| | `bits()` | `int` | Bitmask: bit _i_ = 1 if lane _i_ is true | | `any()` | `bool` | True if any lane is true | | `all()` | `bool` | True if all lanes are true | | `count()` | `int` | Number of true lanes | | `first_set()` | `Option` | Index of first true lane | #### 20.8.4.3 Platform support | Width | x86_64 SSE2 | x86_64 AVX2 | x86_64 AVX-512 | aarch64 NEON | wasm SIMD128 | |-------|------------|-------------|----------------|--------------|--------------| | 128-bit | Native | Native | Native | Native | Native | | 256-bit | Emulated | Native | Native | Emulated | Emulated | | 512-bit | Emulated | Emulated | Native | Emulated | Emulated | The default `def impl Intrinsics` uses native SIMD instructions when available and falls back to scalar emulation otherwise. For testing, `EmulatedIntrinsics` always uses scalar operations. `simd_load_aligned` shall panic if the data offset is not aligned to the vector width boundary (16/32/64 bytes for 128/256/512-bit respectively). NOTE NEON lacks native `movemask`. The `Mask.bits()` implementation on aarch64 uses a polyfill (~4 instructions). #### 20.8.4.4 Feature detection Feature detection via `cpu_has_feature` accepts platform-specific feature strings: | Platform | Features | |----------|----------| | x86_64 | `"sse"`, `"sse2"`, `"sse3"`, `"ssse3"`, `"sse4.1"`, `"sse4.2"`, `"avx"`, `"avx2"`, `"avx512f"`, `"avx512bw"` | | aarch64 | `"neon"` | | wasm32 | `"simd128"` | Unknown feature strings cause a panic. ## 20.9 Default capabilities `Print` has a default implementation via `def impl`. Programs may use `print` without declaring `uses Print`: ```ori @main () -> void = print(msg: "Hello, World!"); ``` The default is `StdoutPrint` for native execution, `BufferPrint` for WASM. ### 20.9.1 Name resolution When resolving a capability name, the compiler checks in order: 1. **Innermost `with...in` binding** — highest priority 2. **Outer `with...in` bindings** — in reverse nesting order 3. **Imported `def impl`** — from the module where the trait is defined 4. **Module-local `def impl`** — defined in the current module 5. **Error** — capability not provided When both an imported `def impl` and a module-local `def impl` exist for the same capability, imported takes precedence. ### 20.9.2 Marker capabilities > **Proposal:** [unsafe-semantics-proposal.md](../../proposals/approved/unsafe-semantics-proposal.md) A _marker capability_ is a capability with no methods, no bindable implementation, and a specific discharge mechanism. Marker capabilities gate operations or contexts — they track what the code does, not what API it uses. Shared semantics for all marker capabilities: - No methods — gates operations, not API surface - Cannot be bound via `with...in` (E1203) - Propagates through the call chain like any capability - Each marker has its own discharge mechanism | Marker | Purpose | Discharge | |--------|---------|-----------| | `Suspend` | May suspend execution | Runtime / concurrency patterns | | `Unsafe` | Bypasses safety guarantees | `unsafe { }` block | Attempting to bind a marker capability is a compile-time error: ```ori with Suspend = SomeImpl in // ERROR: marker capability cannot be bound suspending_fn() with Unsafe = something in // ERROR: marker capability cannot be bound unsafe_fn() ``` `Suspend` context is provided by: - The runtime for `@main () uses Suspend` - Concurrency patterns: `parallel`, `spawn`, `nursery` `Unsafe` context is provided by: - `unsafe { expr }` blocks — programmer assertion of safety - Functions declaring `uses Unsafe` (propagates to callers) See [FFI § Unsafe Expressions](26-ffi.md#265-unsafe-expressions) for the full specification of unsafe operations. ## 20.10 Testing ```ori @test_fetch tests @fetch () -> void = with Http = MockHttp { responses: {"/users/1": "{...}"} } in { assert_ok(result: fetch(url: "/users/1")) } ``` Mock implementations are synchronous; test does not need `Suspend`. ## 20.11 Named capability sets (`capset`) > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (capset_decl) A _capset_ is a named, transparent alias for a set of capabilities. Capsets are expanded to their constituent capabilities during name resolution, before type checking. A capset is not a trait, not a type, and has no runtime representation. ```ori capset Net = Http, Dns, Tls capset Runtime = Clock, Random, Env capset WebService = Net, Runtime, Database, Suspend ``` ### 20.11.1 Usage Capsets may appear in `uses` clauses and other `capset` declarations. Capsets and individual capabilities may be mixed: ```ori @fetch (url: str) -> Result uses Net, Logger, Suspend = ...; ``` After expansion, `uses Net, Logger, Suspend` is equivalent to `uses Http, Dns, Tls, Logger, Suspend`. ### 20.11.2 Expansion The compiler expands capsets transitively and deduplicates the result. The expanded set uses set semantics — duplicates are eliminated and order is irrelevant: ```ori capset Net = Http, Dns capset Infra = Net, Logger // Valid — `uses Infra, Http` expands to `uses Http, Dns, Logger` @fn () -> void uses Infra, Http = ...; ``` ### 20.11.3 Restrictions A capset declaration: - Must contain at least one member - Must not form a cycle with other capset declarations - Must not share a name with a trait in the same scope - Members shall be capability traits or other capsets A capset is not a trait. It cannot be used in `impl` blocks, `def impl` declarations, or `with...in` bindings: ```ori // Invalid — capsets cannot be bound with Net = something in ... // error // Invalid — capsets are not traits impl SomeType: Net { ... } // error def impl Net { ... } // error ``` ### 20.11.4 Visibility Capsets follow standard visibility rules. A `pub` capset shall not reference non-accessible capabilities: ```ori pub capset Net = Http, Dns, Tls // Valid — all members accessible pub capset Bad = SomePrivateTrait // Invalid — member not accessible ``` ### 20.11.5 Variance interaction Capability variance operates on the expanded set: ```ori capset Runtime = Clock, Random, Env @needs_clock () -> void uses Clock = ...; @caller () -> void uses Runtime = { needs_clock(), // Valid — Runtime includes Clock } ``` ## 20.12 Purity Functions without `uses` are pure: no side effects, cannot suspend, safely parallelizable. ## 20.13 Errors | Code | Description | |------|-------------| | E0600 | Function uses capability without declaring it | | E1000 | Conflicting default implementations for same trait | | E1001 | Duplicate default implementation in same module | | E1002 | `def impl` methods cannot have `self` parameter | | E1200 | Missing capability (callee requires capability caller lacks) | | E1201 | Unbound capability (no `with` or `def impl` available) | | E1202 | Type does not implement capability trait | | E1203 | Marker capability cannot be explicitly bound (`Suspend`, `Unsafe`) | | E1204 | Handler missing required operation (trait method not defined in handler) | | E1205 | Handler operation signature mismatch (parameters or return type) | | E1206 | Handler state type inconsistency (operations return different state types) | | E1207 | Handler operation for non-existent trait method | | E1220 | Cyclic capset definition | | E1221 | Empty capset | | E1222 | Capset name collides with trait name | | E1223 | Capset member is not a capability trait or capset | ``` error[E0600]: function uses `Http` without declaring it ``` Capabilities shall be explicitly declared or provided. --- --- title: "Memory Model" description: "Clause 21: Ori Language Specification — Memory Model" order: 21 section: "Language" --- # 21 Memory model Ori uses Automatic Reference Counting (ARC) without cycle detection. This is made possible by language design choices that structurally prevent reference cycles. ## 21.1 Why pure ARC works Most languages using ARC require either cycle detection (Python, PHP) or manual weak reference annotations (Swift, Objective-C). Ori requires neither because its execution model produces directed acyclic graphs (DAGs) by construction. ### 21.1.1 The problem: object graphs Traditional object-oriented languages build **reference graphs** where objects hold references to other objects. Cycles form naturally: ``` ┌──────────┐ │ Parent │ └────┬─────┘ │ children ▼ ┌──────────┐ │ Child │──── parent ───┐ └──────────┘ │ ▲ │ └─────────────────────┘ ``` Common cycle sources in other languages: - Closures capturing `self`/`this` - Parent-child bidirectional references - Observer/delegate callback patterns - Event emitter subscriptions ### 21.1.2 The solution: sequential data flow Ori's block expressions enforce **linear data flow**: ``` input ──▶ step A ──▶ step B ──▶ step C ──▶ output ``` Each binding in a sequence: 1. Holds a value that is never mutated in place (reassignment replaces the value, it does not modify it) 2. Can only reference earlier bindings (forward-only) 3. Is destroyed when the sequence ends ```ori @process (input: Data) -> Result = try { let validated = validate(data: input)?; // A: sees input let enriched = enrich(data: validated)?; // B: sees input, validated let saved = save(data: enriched)?; // C: sees input, validated, enriched Ok(saved) } ``` There is no mechanism for `saved` to reference the function `process`, or for `enriched` and `validated` to reference each other bidirectionally. Data flows forward through transformations. ### 21.1.3 Structural guarantees | Pattern | Data Flow | Cycle Prevention | |---------|-----------|------------------| | `{ a; b; c }` | Linear sequence | Each step sees only prior bindings | | `try { a?; b?; c? }` | Linear with early exit | Same as blocks | | `match x { ... }` | Branching | Each branch is independent | | `for x in xs do ...` | Iteration | Loop variable rebinding, no self-reference | | `loop { ... }` | Iteration | State via mutable bindings, no self-reference | | `parallel(...)` | Fan-out/fan-in | Results collected, no cross-task references | ### 21.1.4 Closures capture by value In languages where closures capture by reference, cycles form when a closure captures `self`: ``` Object ──▶ callback field ──▶ closure ──▶ captured self ──▶ Object ``` Ori closures capture by value. The closure receives a copy of captured data, not a reference back to the containing scope: ```ori let x = 5; let f = () -> x + 1; // f contains a copy of 5, not a reference to x ``` This eliminates the most common source of cycles in functional-style code. ### 21.1.5 Closure representation A closure is represented as a struct containing captured values: ```ori let x = 10; let y = "hello"; let f = () -> `{y}: {x}`; // f is approximately: // type _Closure_f = { captured_x: int, captured_y: str } ``` For reference-counted types (lists, maps, custom types), the closure stores the reference (incrementing the reference count), not a deep copy of the data. ### 21.1.6 Self-referential types forbidden The one place cycles could form is in user-defined recursive types: ```ori // Compile error: self-referential type type Node = { next: Option } ``` If permitted, this would allow: ``` node1.next = Some(node2) node2.next = Some(node1) // cycle ``` Ori forbids this at the type level. Recursive structures use indices into collections: ```ori // Valid: indices for relationships type Graph = { nodes: [NodeData], edges: [(int, int)] } ``` ### 21.1.7 Summary Pure ARC works in Ori because: 1. **Blocks enforce DAGs** — Data flows forward through `{ }` blocks, `try`, `match` 2. **Value capture prevents closure cycles** — No reference back to enclosing scope 3. **Type restrictions prevent structural cycles** — Self-referential types forbidden 4. **No shared mutable references** — Single ownership of mutable data These are not conventions — they are language invariants enforced by the compiler. ## 21.2 Reference counting | Operation | Effect | |-----------|--------| | Value creation | Count = 1 | | Reference copy | Count + 1 | | Reference drop | Count - 1 | | Count = 0 | Value destroyed | References are copied on assignment, argument passing, field storage, return, closure capture. References are dropped when variables go out of scope or are reassigned. ### 21.2.1 Atomicity All reference count operations are atomic. This ensures correct deallocation when values are shared across concurrent tasks. | Operation | Atomic Instruction | Memory Ordering | |-----------|-------------------|-----------------| | Increment | Fetch-add | Relaxed | | Decrement | Fetch-sub | Release | | Deallocation check | Fence before free | Acquire | Increment uses Relaxed ordering because the incrementing thread already has access to the object. Decrement uses Release ordering to ensure all prior writes to the object are visible to other threads that may subsequently decrement. The Acquire fence before deallocation ensures the deallocating task observes all prior writes to the object from all other tasks. The observable behavior shall be identical regardless of whether atomic or non-atomic operations are used. NOTE An implementation may use non-atomic operations for values that provably do not escape the current task. This is an optimization; the observable behavior is identical. ## 21.3 Destruction Destruction occurs when values become unreachable, no later than scope end. ### 21.3.1 The Drop trait The `Drop` trait enables custom destruction logic: ```ori trait Drop { @drop (self) -> void; } ``` When a value's reference count reaches zero, its `Drop.drop` method is called if implemented. Drop is called before memory is reclaimed. `Drop` is included in the prelude. ### 21.3.2 Destructor timing Destructors run when reference count reaches zero: | Context | Timing | |---------|--------| | Local binding out of scope | Immediately at scope end | | Last reference dropped | Immediately after drop | | Field of struct dropped | After struct destructor | | Collection element | When removed or collection dropped | Values may be dropped before scope end if no longer referenced (compiler optimization). ### 21.3.3 Destruction order Reverse creation order within a scope: ```ori { let a = create_a(); // Destroyed 3rd let b = create_b(); // Destroyed 2nd let c = create_c(); // Destroyed 1st // destroyed: c, b, a } ``` Struct fields are destroyed in reverse declaration order: ```ori type Container = { first: Resource, // Destroyed 3rd second: Resource, // Destroyed 2nd third: Resource, // Destroyed 1st } ``` List elements are destroyed back-to-front: ```ori let items = [a, b, c]; // When dropped: c, then b, then a ``` Tuple elements are destroyed right-to-left: ```ori let tuple = (first, second, third); // When dropped: third, then second, then first ``` Map entries have no guaranteed destruction order (hash-based). ### 21.3.4 Panic during destruction If a destructor panics during normal execution (not already unwinding): 1. That panic propagates normally 2. Other values in scope still have their destructors run 3. Each destructor runs in isolation If a destructor panics while already unwinding from another panic (double panic): 1. The program **aborts** immediately 2. No further destructors run 3. Exit code indicates abnormal termination ### 21.3.5 Async destructors Destructors cannot be async: ```ori impl Resource: Drop { @drop (self) -> void uses Suspend = ...; // ERROR: drop cannot be async } ``` For async cleanup, use explicit methods: ```ori impl AsyncResource { @close (self) -> void uses Suspend = ...; // Explicit async cleanup } impl AsyncResource: Drop { @drop (self) -> void = (); // Synchronous no-op } ``` ### 21.3.6 Destructors and task cancellation When a task is cancelled, destructors still run during unwinding. ## 21.4 Reference counting optimizations An implementation may optimize reference counting operations provided the following observable behavior is preserved: 1. Every reference-counted value is deallocated no later than the end of the scope in which it becomes unreachable 2. `Drop.drop` is called exactly once per value, in the order specified by [§ Destruction Order](#2133-destruction-order) 3. No value is accessed after deallocation ### 21.4.1 Permitted optimizations The following optimizations are permitted: | Optimization | Description | |-------------|-------------| | Scalar elision | No reference counting operations for scalar types (see [§ Type Classification](#217-type-classification)) | | Borrow inference | Omit increment/decrement for parameters that are borrowed and do not outlive the callee | | Move optimization | Elide the increment/decrement pair when a value is transferred on last use | | Redundant pair elimination | Remove an increment immediately followed by a decrement on the same value | | Constructor reuse | Reuse the existing allocation when the reference count is one (requires a runtime uniqueness check) | | Copy-on-write | Mutating operations on collections and strings check reference uniqueness at runtime. If the reference count is one, the operation mutates in place; if shared, the operation copies before mutating. | | Seamless slicing | Slice operations (`take`, `skip`, `slice`, `substring`, `trim`) may return a zero-copy view into the original allocation rather than copying elements. The view shares the original allocation's reference count. | | Small value inlining | Small values (e.g., short strings ≤23 bytes) may be stored inline without heap allocation. The threshold is implementation-defined. | | Early drop | Deallocate a value before scope end when it is provably unreferenced for the remainder of the scope | | Tail-modulo-cons (TRMC) | Tail-recursive functions that construct values can rewrite allocation patterns to build results in-place, avoiding intermediate allocations. | | Functional in-place (FIP) | Functions that consume a uniquely-owned argument and produce a structurally similar result may be certified as functional-in-place, enabling allocation-free execution. | These are permissions, not requirements. A conforming implementation may perform all, some, or none of these optimizations. NOTE Copy-on-write preserves value semantics: `let b = a; b = b.push(value: x)` shall not modify `a`, regardless of whether the implementation copies or mutates in place. The optimization is transparent to user code. ### 21.4.2 AIMS — ARC Intelligent Memory System The reference implementation uses AIMS, a unified semantic framework that replaces traditional sequential optimization passes (borrow inference → liveness → RC insertion → reuse → elimination) with a single abstract interpreter over a multi-dimensional product lattice. All memory facts about every variable — ownership, consumption pattern, usage count, uniqueness proof, escape scope, reuse shape, and effect classification — are computed simultaneously in one converging backward dataflow analysis. All outputs (RC operations, reuse tokens, copy-on-write annotations, drop hints, FIP certification) are projections of the same converged state. NOTE AIMS is an implementation strategy, not a language requirement. A conforming implementation may use any optimization approach that preserves the observable behavior specified above. ## 21.5 Ownership and borrowing Every reference-counted value has exactly one _owner_. The owner is the binding, field, or container element that holds the value. ### 21.5.1 Ownership transfer Ownership transfers on: - Assignment to a new binding - Passing as a function argument - Returning from a function - Storage in a container element or struct field On transfer, the previous owner relinquishes access. The reference count does not change; ownership moves without an increment/decrement pair. ### 21.5.2 Borrowed references A _borrowed reference_ provides temporary read access to a value without incrementing the reference count. A borrowed reference shall not outlive its owner. The compiler infers ownership and borrowing. There is no user-visible syntax for ownership annotations or borrow markers. ## 21.6 Cycle prevention Cycles prevented at compile time: 1. Values are never mutated in place — reassignment produces new values, preventing in-place cycle formation 2. No shared mutable references — single ownership of mutable data 3. Self-referential types forbidden ```ori // Valid: indices type Graph = { nodes: [Node], edges: [(int, int)] } // Error: self-referential type Node = { next: Option } // compile error ``` ## 21.7 Type classification Every type is classified as either _scalar_ or _reference_ for the purpose of reference counting. Classification is determined by type containment, not by representation size. ### 21.7.1 Scalar types A type is scalar if it requires no reference counting. The following types are scalar: - Primitive types: `int`, `float`, `bool`, `char`, `byte`, `Duration`, `Size`, `Ordering` - `unit` and `never` - Compound types (structs, enums, tuples, `Option`, `Result`, `Range`) whose fields are all scalar ### 21.7.2 Reference types A type is a reference type if it requires reference counting. The following types are reference types: - Heap-allocated types: `str`, `[T]`, `{K: V}`, `Set`, `Channel` - Function types and iterator types - Compound types containing at least one reference type field ### 21.7.3 Transitive rule Classification is transitive: if any field of a compound type is a reference type, the compound type is a reference type. | Type | Classification | Reason | |------|---------------|--------| | `int` | Scalar | Primitive | | `(int, float, bool)` | Scalar | All fields scalar | | `{ x: int, y: int }` | Scalar | All fields scalar | | `str` | Reference | Heap-allocated | | `{ id: int, name: str }` | Reference | `name` is reference | | `Option` | Reference | Inner type is reference | | `Option` | Scalar | Inner type is scalar | | `[int]` | Reference | List is heap-allocated | | `Result` | Reference | `str` is reference | Classification is independent of type size. A struct with ten `int` fields is scalar. A struct with one `str` field is a reference type regardless of its total size. ### 21.7.4 Value types A type that implements the `Value` trait is stored inline (bitwise copy, no reference counting, no `Drop`). All fields of a `Value` type shall themselves be `Value`. The `Value` trait implies `Clone` and `Sendable`. Primitive scalar types (`int`, `float`, `bool`, `char`, `byte`, `void`, `Duration`, `Size`, `Ordering`) implicitly satisfy `Value`. User-defined types opt in via the type declaration: ```ori type Point: Value, Eq = { x: float, y: float } ``` A `Value` type shall not exceed 512 bytes. Types exceeding 256 bytes produce a warning. ### 21.7.5 Generic type parameters Unresolved type parameters are conservatively treated as reference types. After monomorphization, all type parameters are concrete and classification is exact. ## 21.8 Constraints - Self-referential types are compile errors - Destruction in reverse creation order - Values destroyed when reference count reaches zero ## 21.9 ARC safety invariants Ori uses ARC without cycle detection. The following invariants shall be maintained by all language features to ensure ARC remains viable. ### 21.9.1 Invariant 1: value capture Closures shall capture variables by value. Reference captures are prohibited. ```ori let x = 5; let f = () -> x + 1; // captures copy of x, not reference to x ``` This prevents cycles through closure environments. ### 21.9.2 Invariant 2: no implicit back-references Structures shall not implicitly reference their containers. Bidirectional relationships require explicit weak references or indices. ```ori // Valid: indices for back-navigation type Tree = { nodes: [Node], parent_indices: [Option] } // Invalid: implicit parent reference would create cycle type Node = { children: [Node], parent: Node } // error ``` ### 21.9.3 Invariant 3: no shared mutable references Multiple mutable references to the same value are prohibited. Shared access requires either: - Copy-on-write semantics - Explicit synchronization primitives with single ownership ### 21.9.4 Invariant 4: value semantics default Types have value semantics unless explicitly boxed. Reference types require explicit opt-in through container types or `Box`. ### 21.9.5 Invariant 5: explicit weak references If weak references are added to the language, they shall: - Use distinct syntax (`Weak`) - Require explicit upgrade operations returning `Option` - Never be implicitly created ### 21.9.6 Task isolation Values shared across task boundaries are reference-counted. Each task may independently increment and decrement the reference count of a shared value. Atomic reference count operations (see [§ Atomicity](#2121-atomicity)) ensure that deallocation occurs exactly once, regardless of which task drops the last reference. A task shall not hold a borrowed reference to a value owned by another task. All cross-task value sharing uses ownership transfer or reference count increment. See [Concurrency Model § Task Isolation](22-concurrency-model.md#2213-task-isolation) for task isolation rules. ### 21.9.7 Handler frame state Stateful handlers (see [Capabilities § Stateful Handlers](20-capabilities.md#205-stateful-handlers)) maintain frame-local mutable state within a `with...in` scope. This state is analogous to mutable loop variables: it is local to the handler frame, not aliased, and not accessible outside the `with...in` scope. Handler frame state does not violate Invariant 3 (no shared mutable references) because the state has a single owner (the handler frame) and is never shared. ### 21.9.8 Feature evaluation New language features shall be evaluated against these invariants. A feature that violates any invariant shall either: 1. Be redesigned to maintain the invariant 2. Provide equivalent cycle prevention guarantees 3. Be rejected --- --- title: "Concurrency Model" description: "Clause 22: Ori Language Specification — Concurrency Model" order: 22 section: "Language" --- # 22 Concurrency model Task-based concurrency with cooperative scheduling and explicit suspension points. > **Grammar:** See [grammar.ebnf](grammar.md) § PATTERNS (parallel, spawn, nursery) ## 22.1 Tasks A _task_ is an independent unit of concurrent execution. ### 22.1.1 Properties A task has: 1. **Its own call stack** — function calls within a task are sequential 2. **Isolated mutable state** — no task can directly access another task's mutable bindings 3. **Cooperative scheduling** — tasks yield control at suspension points 4. **Bounded lifetime** — tasks are created within a scope and shall complete before that scope exits Tasks are not threads. Multiple tasks may execute on the same OS thread, or the runtime may distribute tasks across OS threads. The runtime determines task-to-thread mapping. ### 22.1.2 Task creation Tasks are created exclusively by concurrency patterns: | Pattern | Creates Tasks | Description | |---------|--------------|-------------| | `parallel(tasks: [...])` | Yes | One task per list element | | `spawn(tasks: [...])` | Yes | Fire-and-forget tasks | | `nursery(body: n -> ...)` | Yes, via `n.spawn()` | Structured task spawning | | Regular function call | No | Same task, same stack | See [Patterns](15-patterns.md) for pattern definitions. ### 22.1.3 Task isolation Each task has private mutable bindings. Bindings captured across task boundaries shall be `Sendable` and become inaccessible in the spawning scope: ```ori @example () -> void uses Suspend = { let x = 0; parallel( tasks: [ () -> {x = 1}, // error: cannot capture mutable binding across task boundary () -> {x = 2} ] ) } ``` See [Memory Model § Task Isolation](21-memory-model.md#2196-task-isolation) for isolation guarantees. ## 22.2 Suspending context A _suspending context_ is a runtime environment that can execute suspending functions, schedule suspension and resumption, and manage multiple concurrent tasks. ### 22.2.1 Establishing suspending context A suspending context is established by: - **The runtime** — when `@main` declares `uses Suspend`, the runtime provides the initial suspending context - **Concurrency patterns** — `parallel`, `spawn`, and `nursery` create nested suspending contexts for spawned tasks A function declaring `uses Suspend` _requires_ a suspending context to execute — it does not establish one. The `Suspend` capability indicates the function may suspend, requiring a scheduler to manage resumption. ### 22.2.2 @main and Suspend Programs using concurrency patterns shall have `@main` declare `uses Suspend`: ```ori // Valid: main declares Suspend @main () -> void uses Suspend = { parallel(tasks: [task_a(), task_b()]) } // Invalid: main uses concurrency without Suspend @main () -> void = { parallel(tasks: [task_a(), task_b()]); // error: requires Suspend capability } ``` The runtime establishes the suspending context when `@main uses Suspend` is declared. ## 22.3 Suspension points A _suspension point_ is a location where a task may yield control to the scheduler. ### 22.3.1 Where suspension occurs Suspension points occur ONLY at: 1. **Suspending function calls** — calling a function with `uses Suspend` 2. **Channel operations** — `send` and `receive` on channels 3. **Explicit yield** — within `parallel`, `spawn`, or `nursery` body evaluation ### 22.3.2 Where suspension cannot occur Suspension NEVER occurs: - In the middle of expression evaluation - During non-suspending function execution - At arbitrary points chosen by the runtime This provides _predictable interleaving_ — atomicity boundaries are explicit. ## 22.4 Suspend propagation A function that calls suspending code shall itself be suspending: ```ori @caller () -> int uses Suspend = callee(); // OK: caller can suspend @caller_sync () -> int = callee(); // error: callee uses Suspend but caller does not @callee () -> int uses Suspend = ...; ``` The `Suspend` capability _propagates upward_ — callers of suspending functions shall declare `uses Suspend`. ## 22.5 Blocking in suspending context A non-suspending function called from a suspending context executes synchronously, blocking that task but not other tasks: ```ori @expensive_sync () -> int = heavy_math(); // Long computation, no suspension points @main () -> void uses Suspend = { parallel( tasks: [ () -> expensive_sync(), // This task blocks during computation () -> other_work(), // This task can run concurrently ] ) } ``` ## 22.6 Capture and ownership Task closures follow capture-by-value semantics. When a value is captured by a task closure, the original binding becomes inaccessible: ```ori @capture_example () -> void uses Suspend = { let data = create_data(); nursery( body: n -> { n.spawn(task: () -> process(data)); // data captured by value print(msg: data.field); // error: data is no longer accessible } ) } ``` Bindings captured across task boundaries become inaccessible in the spawning scope to prevent data races. This uses existing capture-by-value behavior with an additional constraint. ### 22.6.1 Sendable requirement Captured values shall implement `Sendable`: ```ori @spawn_example () -> void uses Suspend = { let data = create_data(); // Data: Sendable nursery( body: n -> n.spawn(task: () -> process(data)), ) } ``` See [Types § Sendable Trait](08-types.md#814-sendable-trait) for `Sendable` definition. ### 22.6.2 Reference count atomicity When values cross task boundaries, reference count operations are atomic. The compiler ensures thread-safe reference counting for values accessed by multiple tasks. ## 22.7 Cancellation Task cancellation uses a _cooperative_ model. A cancelled task continues executing until it reaches a cancellation checkpoint, then terminates with cleanup. ### 22.7.1 Cancellation checkpoints A task observes cancellation at these points: | Checkpoint | Description | |------------|-------------| | Suspension points | Suspending calls, channel operations | | Loop iterations | Start of each `for` or `loop` iteration | | Pattern entry | Entry to `run`, `try`, `match`, `parallel`, `nursery` | Between checkpoints, a task executes atomically with respect to cancellation. ### 22.7.2 Cancellation behavior When a task reaches a checkpoint while marked for cancellation: 1. The current expression evaluates to `Err(CancellationError)` 2. Normal unwinding occurs — destructors run 3. The task terminates with `Err(CancellationError)` ### 22.7.3 CancellationError type ```ori type CancellationError = { reason: CancellationReason, task_id: int, } type CancellationReason = | Timeout | SiblingFailed | NurseryExited | ExplicitCancel | ResourceExhausted; ``` ### 22.7.4 Error mode semantics **FailFast:** On first error, all other tasks are marked for cancellation. The nursery waits for all tasks to reach checkpoints and terminate. **CancelRemaining:** On first error, pending tasks (not yet started) are cancelled immediately. Running tasks continue to completion. **CollectAll:** Errors do not trigger cancellation. All tasks run to completion. ### 22.7.5 Timeout cancellation When a nursery timeout expires: 1. All incomplete tasks are marked for cancellation 2. Tasks reach checkpoints and terminate 3. Nursery waits for cancellation to complete 4. Returns results collected so far Timeout always cancels incomplete tasks regardless of error mode. ### 22.7.6 Cleanup guarantees When a task is cancelled: 1. Stack unwinding occurs from the cancellation checkpoint 2. Destructors run for all values in scope 3. Cleanup is guaranteed to complete before task terminates A task cannot be forcibly terminated during destructor execution. ### 22.7.7 Checking cancellation status Tasks can explicitly check cancellation status: ```ori @is_cancelled () -> bool; ``` This built-in function returns `true` if the current task has been marked for cancellation. ```ori @long_running_task () -> Result uses Suspend = { for item in large_dataset do { if is_cancelled() then break Err(CancellationError { ... }) process(item) } } ``` ### 22.7.8 Nested nurseries When an outer nursery cancels a task containing an inner nursery: 1. The inner nursery receives cancellation 2. Inner nursery cancels its tasks per its error mode 3. Inner nursery completes (with cancellation results) 4. Outer task then completes ## 22.8 Errors ``` error[E0700]: cannot capture mutable binding across task boundary --> example.ori:5:15 | 5 | () -> { x = 1 }, | ^^^^^^^^^^^ = note: mutable bindings cannot be shared between tasks error[E0701]: callee uses Suspend but caller does not --> example.ori:3:5 | 3 | async_fn() | ^^^^^^^^^^ = help: add `uses Suspend` to the function signature error[E0702]: concurrency pattern requires Suspend capability --> example.ori:2:5 | 2 | parallel(tasks: [...]) | ^^^^^^^^^^^^^^^^^^^^^^ = help: add `uses Suspend` to @main ``` --- --- title: "Program Execution" description: "Clause 23: Ori Language Specification — Program Execution" order: 23 section: "Language" --- # 23 Program execution A _program_ is a complete, executable Ori application. > **Grammar:** See [grammar.ebnf](grammar.md) § PROGRAM ENTRY (main_function) ## 23.1 Entry point Every executable program shall have exactly one `@main` function. Valid signatures: ```ori @main () -> void = ...; @main () -> int = ...; @main (args: [str]) -> void = ...; @main (args: [str]) -> int = ...; ``` The `args` parameter, if present, contains command-line arguments passed to the program. It does not include the program name. ```ori // invoked as: ori run program.ori hello world @main (args: [str]) -> void = { // args = ["hello", "world"] print(args[0]), // prints "hello" } ``` ## 23.2 Initialization Program initialization proceeds in the following order: 1. All modules are initialized eagerly 2. Config variables (`$name`) are evaluated in dependency order 3. `@main` is invoked ### 23.2.1 Config variables Config variables are compile-time constants evaluated before program execution. ```ori $timeout = 30s; $max_retries = 3; $double_timeout = $timeout * 2; // references other config ``` Config variables may reference other config variables. The compiler determines evaluation order from dependencies. Circular dependencies are an error. ### 23.2.2 Module initialization All imported modules are initialized before `@main` runs. Modules form a directed acyclic graph via import declarations. The initialization order is determined by a deterministic topological sort of this graph: a module is initialized only after all modules it imports have been initialized. Ties in the sort order are broken by import declaration order. Circular module dependencies are a compile-time error. Module-level constant initializers shall be pure expressions (see [12.2](12-constants.md#122-module-level-constants)). This ensures that initialization order does not affect observable behavior. ## 23.3 Termination ### 23.3.1 Normal termination A program terminates normally when `@main` returns. | Return type | Exit code | |-------------|-----------| | `void` | 0 | | `int` | Returned value | Exit codes outside the range 0–255 are truncated to the low 8 bits. On normal termination, `Drop` implementations run for all live values in reverse declaration order (LIFO). All `Drop` implementations shall complete before the process exits. ### 23.3.2 Panic termination A program terminates abnormally when an unhandled panic occurs. On panic, the runtime executes the following sequence: 1. Construct a `PanicInfo` value with message, location, and stack trace 2. If an `@panic` handler is defined, call it (see 23.4) 3. Print the error message to stderr 4. Print the stack trace to stderr 5. Exit with code 1 `Drop` implementations do _not_ run during panic. Ori uses an abort model: panics terminate immediately without unwinding the stack. NOTE Panics represent bugs in the program. Use `Result` for recoverable errors. See [Clause 17](17-errors-and-panics.md) for the error handling model. ### 23.3.3 Standard streams - `print(msg:)` writes to stdout, followed by a newline - Panic messages and stack traces write to stderr - `print(msg:)` inside an `@panic` handler writes to stderr - There is no built-in for reading stdin; use the `std.io` module ## 23.4 Panic handler A program may define an optional `@panic` handler: ```ori @panic (info: PanicInfo) -> void = { print(msg: `FATAL: {info.message}`); } ``` The `@panic` handler, if present, is called before the default panic behavior. At most one `@panic` handler shall exist per program. A panic inside the `@panic` handler (a _double panic_) causes immediate termination with no further processing. A panic during a `Drop` implementation also causes immediate termination. The `PanicInfo` type is defined in the prelude: ```ori type PanicInfo = { message: str, location: TraceEntry, stack_trace: [TraceEntry], thread_id: Option, } ``` ## 23.5 Runtime panic catalogue The following table lists all conditions that cause a runtime panic. Each condition is normative: an implementation shall panic under exactly these circumstances. | Condition | Clause | Message pattern | |-----------|--------|----------------| | Integer overflow (add, sub, mul, neg) | [14.3](14-expressions.md) | "integer overflow" | | Integer division by zero | [14.3](14-expressions.md) | "division by zero" | | Integer modulo by zero | [14.3](14-expressions.md) | "modulo by zero" | | `int.min / -1` or `int.min % -1` | [14.3](14-expressions.md) | "integer overflow" | | Shift count negative | [14.3](14-expressions.md) | "negative shift count" | | Shift count ≥ bit width | [14.3](14-expressions.md) | "shift count exceeds bit width" | | `1 << 63` (shift overflow) | [14.3](14-expressions.md) | "shift overflow" | | List index out of bounds | [14.1.2](14-expressions.md) | "index out of bounds: N, length M" | | String index out of bounds | [14.1.2](14-expressions.md) | "index out of bounds" | | `as` conversion out of range | [14.1.5](14-expressions.md) | "conversion out of range" | | `unwrap()` on `None` | [9](09-properties-of-types.md) | "unwrap called on None" | | `unwrap()` on `Err` | [9](09-properties-of-types.md) | "unwrap called on Err" | | `panic(msg:)` | [Annex C](annex-c-built-in-functions.md) | User-provided message | | `todo()` | [Annex C](annex-c-built-in-functions.md) | "not yet implemented" | | `unreachable()` | [Annex C](annex-c-built-in-functions.md) | "entered unreachable code" | | `assert` failure | [Annex C](annex-c-built-in-functions.md) | "assertion failed" | | `assert_eq` / `assert_ne` failure | [Annex C](annex-c-built-in-functions.md) | "assertion failed: ..." | | Range step is zero | [14.6](14-expressions.md) | "step cannot be zero" | | Fixed-capacity list push when full | [8](08-types.md) | "list is full" | | Stack overflow | 23 | "stack overflow" | | Pre-condition violation | [14](14-expressions.md) | "pre-condition failed: ..." | | Post-condition violation | [14](14-expressions.md) | "post-condition failed: ..." | All panics produce a `PanicInfo` value with message, source location, and stack trace. Panics are not recoverable. There is no `catch` mechanism for panics. Use `Result` for recoverable errors and `assert_panics` in tests to verify panic behavior. ## 23.6 Stack depth The maximum call stack depth is implementation-defined. An implementation shall support at least 1 000 stack frames. Exceeding the stack depth limit causes a "stack overflow" panic. --- --- title: "Constant Expressions" description: "Clause 24: Ori Language Specification — Constant Expressions" order: 24 section: "Language" --- # 24 Constant expressions A _constant expression_ is an expression that can be fully evaluated at compile time. > **Grammar:** See [grammar.ebnf](grammar.md) § CONSTANT EXPRESSIONS, DECLARATIONS (const_function) ## 24.1 Constant contexts Constant expressions are required in: - Config variable initializers (`$name = expr`) - Fixed-size array lengths - Const generic parameters - Attribute arguments ## 24.2 Allowed in constant expressions The following are valid in constant expressions: ### 24.2.1 Literals All literal values: ```ori $count = 42; $name = "config"; $enabled = true; $rate = 3.14; $timeout = 30s; $buffer = 4kb; ``` ### 24.2.2 Arithmetic and logic Operators on constant operands: ```ori $double = $count * 2; $offset = $base + 100; $flag = $debug && $verbose; ``` ### 24.2.3 String concatenation ```ori $prefix = "app"; $full_name = $prefix + "_config"; ``` ### 24.2.4 References to config variables Config variables may reference other config variables: ```ori $base_timeout = 30s; $extended_timeout = $base_timeout * 2; ``` The compiler evaluates config variables in dependency order. Circular dependencies are an error. ### 24.2.5 Conditionals ```ori $timeout = if $debug then 60s else 30s; $level = if $verbose then "debug" else "info"; ``` ### 24.2.6 Const function calls Calls to const functions with constant arguments: ```ori $factorial_10 = $factorial(n: 10); ``` ## 24.3 Const functions A _const function_ can be evaluated at compile time. Const functions use the `$` sigil: ```ori $square (x: int) -> int = x * x; $factorial (n: int) -> int = if n <= 1 then 1 else n * $factorial(n: n - 1); $max (a: int, b: int) -> int = if a > b then a else b; ``` The `$` sigil indicates compile-time evaluation, consistent with config variables. ### 24.3.1 Allowed operations Const functions may: - Use arithmetic, comparisons, and boolean logic - Use conditionals (`if`, `match`) - Call other const functions - Use recursion - Use blocks `{ }` for sequencing - Construct structs and collections - Manipulate strings - Use local mutable bindings - Use loop expressions (`for`, `loop`) ### 24.3.2 Restrictions Const functions shall not: - Use capabilities (`uses` clause) - Perform I/O or side effects - Call non-const functions - Access external data - Use random values - Access current time ### 24.3.3 Local mutable bindings Const functions may use mutable bindings for local computation. Local mutation is deterministic — given the same inputs, the function produces the same output: ```ori $sum_to (n: int) -> int = { let total = 0; for i in 1..=n do total = total + i; total } $sum_squares (n: int) -> int = { let result = 0; for i in 1..=n do result = result + i * i; result } ``` ### 24.3.4 Compile-time evaluation When a const function is called with constant arguments, evaluation occurs at compile time: ```ori $power (base: int, exp: int) -> int = if exp == 0 then 1 else base * $power(base: base, exp: exp - 1); $kb = $power(base: 2, exp: 10); // evaluated to 1024 at compile time ``` When called with runtime arguments, the function executes at runtime: ```ori let user_exp = read_int(); let result = $power(base: 2, exp: user_exp); // evaluated at runtime ``` ### 24.3.5 Partial evaluation When a const function is called with some constant and some runtime arguments, the compiler shall evaluate the constant portions at compile time where doing so produces equivalent results: ```ori $multiply (a: int, b: int) -> int = a * b; // Both args const → evaluated at compile time let $twelve = $multiply(a: 3, b: 4); // Compiles to: let $twelve = 12 // One arg runtime → evaluated at runtime with const folded @compute (x: int) -> int = $multiply(a: x, b: 4); // Compiles to: x * 4 ``` This is required behavior. ## 24.4 Evaluation limits The compiler enforces limits on const evaluation to ensure compilation terminates: | Limit | Default | Description | |-------|---------|-------------| | Step limit | 1,000,000 | Maximum expression evaluations | | Recursion depth | 1,000 | Maximum stack frames | | Memory limit | 100 MB | Maximum memory allocation | | Time limit | 10 seconds | Maximum wall-clock time | When a limit is exceeded, compilation fails with an error indicating which limit was exceeded and which expression caused the failure. ### 24.4.1 Configurable limits Projects may adjust limits via configuration: ```toml [const_eval] max_steps = 10000000 max_depth = 2000 max_memory = "500mb" max_time = "60s" ``` Per-expression overrides use the `#const_limit` attribute: ```ori #const_limit(steps: 5000000) let $large_table = $generate_lookup_table(); ``` ## 24.5 Error handling ### 24.5.1 Panics A panic during const evaluation is a compilation error: ```ori $divide (a: int, b: int) -> int = a / b; let $oops = $divide(a: 1, b: 0); // Compilation error: division by zero ``` ### 24.5.2 Integer overflow Integer overflow during const evaluation follows runtime rules and results in a compilation error: ```ori let $big = $multiply(a: 1000000000000, b: 1000000000000); // Compilation error: integer overflow in const evaluation ``` ### 24.5.3 Option and Result Const functions may return `Option` or `Result`: ```ori $safe_div (a: int, b: int) -> Option = if b == 0 then None else Some(a / b); let $result = $safe_div(a: 10, b: 0); // $result = None (at compile time) ``` ## 24.6 Caching Evaluated const expressions are cached by the compiler: 1. Cache key: hash of function body and argument values 2. Subsequent compilations reuse cached results 3. Cache invalidated when function source changes When a library exports const values, the compiled artifact contains the evaluated values, not the computation. The serialization format for const values is implementation-defined. ## 24.7 Not allowed in constant expressions The following are not valid in constant expressions: - Runtime variable references - Non-const function calls - Capability usage - Error propagation (`?`) - I/O operations - Random values - Current time access ```ori $invalid = read_file("config.txt"); // error: not a constant expression $also_invalid = some_list[0]; // error: runtime value ``` ## 24.8 Diagnostics | Code | Description | |------|-------------| | E0500 | Step limit exceeded | | E0501 | Recursion depth exceeded | | E0502 | Memory limit exceeded | | E0503 | Time limit exceeded | | E0504 | Non-const operation in const function | --- --- title: "Conditional Compilation" description: "Clause 25: Ori Language Specification — Conditional Compilation" order: 25 section: "Language" --- # 25 Conditional compilation Conditional compilation enables code to be included or excluded based on target platform, architecture, or build configuration. > **Grammar:** See [grammar.ebnf](grammar.md) § ATTRIBUTES ## 25.1 Overview Two attribute forms control conditional compilation: | Attribute | Purpose | |-----------|---------| | `#target(...)` | Platform and architecture conditions | | `#cfg(...)` | Build configuration flags | Code in false conditions is parsed but not type-checked. Code in true conditions is type-checked and compiled. ## 25.2 Target conditions ### 25.2.1 Operating system ```ori #target(os: "linux") #target(os: "macos") #target(os: "windows") #target(os: "freebsd") #target(os: "android") #target(os: "ios") ``` ### 25.2.2 Architecture ```ori #target(arch: "x86_64") #target(arch: "aarch64") #target(arch: "arm") #target(arch: "wasm32") #target(arch: "riscv64") ``` ### 25.2.3 Target families Target families group related operating systems: | Family | Operating Systems | |--------|-------------------| | `unix` | linux, macos, freebsd, openbsd, netbsd, android, ios | | `windows` | windows | | `wasm` | wasm32, wasm64 | ```ori #target(family: "unix") @get_home_dir () -> str = Env.get("HOME").unwrap_or("/home"); ``` ### 25.2.4 Combined conditions (AND) Multiple conditions in one attribute require all to match: ```ori #target(os: "linux", arch: "x86_64") @linux_x64_only () -> void = ...; ``` Multiple attributes also combine with AND: ```ori #target(os: "linux") #target(arch: "x86_64") @linux_x64_only () -> void = ...; ``` ### 25.2.5 OR conditions The `any_*` variants match any value in a list: ```ori #target(any_os: ["linux", "macos", "freebsd"]) @unix_like () -> void = ...; #target(any_arch: ["x86_64", "aarch64"]) @desktop_arch () -> void = ...; ``` ### 25.2.6 Negation The `not_*` prefix negates a condition: ```ori #target(not_os: "windows") @non_windows () -> void = ...; #target(not_arch: "wasm32") @native_only () -> void = ...; #target(not_family: "wasm") @native_platform () -> void = ...; ``` ## 25.3 Configuration flags ### 25.3.1 Build mode ```ori #cfg(debug) @debug_only () -> void = ...; #cfg(release) @release_only () -> void = ...; #cfg(not_debug) @optimized () -> void = ...; ``` ### 25.3.2 Feature flags ```ori #cfg(feature: "ssl") @secure_connect () -> void = ...; #cfg(feature: "async") type AsyncRuntime = ...; #cfg(not_feature: "ssl") @insecure_fallback () -> void = ...; ``` Feature names shall be valid Ori identifiers: - Start with a letter or underscore - Contain only letters, digits, and underscores - Case-sensitive ### 25.3.3 OR for features ```ori #cfg(any_feature: ["ssl", "tls"]) @secure_connection () -> void = ...; ``` ## 25.4 Applicable items Conditional compilation applies to: | Item | Example | |------|---------| | Functions | `#target(os: "linux") @platform_func () -> void` | | Types | `#target(os: "windows") type Handle = int` | | Trait implementations | `#target(os: "linux") impl Socket: FileDescriptor` | | Constants | `#cfg(debug) let $log_level = "debug"` | | Imports | `#target(os: "linux") use "./linux/io" { epoll_create }` | ## 25.5 File-level conditions The `#!` prefix applies a condition to the entire file: ```ori #!target(os: "linux") // Everything in this file is Linux-only @epoll_create () -> int = ...; @epoll_wait (fd: int) -> [Event] = ...; ``` File-level conditions shall appear before any declarations (after comments and doc comments). ## 25.6 Compile-time constants Target information is available as compile-time constants: | Constant | Type | Description | |----------|------|-------------| | `$target_os` | `str` | Operating system ("linux", "macos", etc.) | | `$target_arch` | `str` | Architecture ("x86_64", "aarch64", etc.) | | `$target_family` | `str` | Target family ("unix", "windows", "wasm") | | `$debug` | `bool` | True in debug builds | | `$release` | `bool` | True in release builds | ### 25.6.1 Usage in expressions ```ori @get_path_separator () -> str = if $target_os == "windows" then "\\" else "/"; ``` Branches conditioned on compile-time constants are eliminated. The false branch is not type-checked: ```ori @get_window_handle () -> WindowHandle = if $target_os == "windows" then WinApi.get_hwnd() // Only type-checked on Windows else panic(msg: "Not supported"); ``` ## 25.7 Compilation semantics ### 25.7.1 Dead code elimination Code in false conditions is completely eliminated from the binary: ```ori #target(os: "linux") @linux_func () -> void = ...; // Not in Windows binary #cfg(debug) let $verbose = true; // Not in release binary ``` ### 25.7.2 Parse vs type-check Code in false conditions is: - **Parsed**: Syntax errors are reported regardless of condition - **Not type-checked**: Type errors in unused code do not trigger ```ori #target(os: "nonexistent") @broken () -> void = this_is_not_valid!@#$; // Parse error, even if not compiled #target(os: "windows") @windows_only () -> void = WindowsApi.call(); // Only type-checked when targeting Windows ``` ## 25.8 Command line ```bash # Target specification ori build --target linux-x86_64 ori build --target macos-aarch64 # Features ori build --feature ssl --feature async ori build --no-default-features --feature minimal # Build mode ori build --debug # sets cfg(debug) ori build --release # sets cfg(release) # Custom cfg flags ori build --cfg experimental ``` ## 25.9 Project configuration ```toml # ori.toml [package] name = "myapp" version = "1.0.0" [features] default = ["ssl"] ssl = [] async = ["dep:async-runtime"] [target.linux] dependencies = ["libc"] [target.windows] dependencies = ["winapi"] ``` ## 25.10 Error codes | Code | Description | |------|-------------| | E0930 | Invalid target OS | | E0931 | Invalid target architecture | | E0932 | Invalid feature name | | E0933 | File-level condition must be first | --- --- title: "Foreign Function Interface" description: "Clause 26: Ori Language Specification — Foreign Function Interface" order: 26 section: "Language" --- # 26 Foreign function interface The foreign function interface (FFI) enables Ori programs to call functions implemented in other languages. > **Grammar:** See [grammar.ebnf](grammar.md) § DECLARATIONS (extern_block) ## 26.1 Overview Ori supports two FFI backends: | Backend | Syntax | Target | |---------|--------|--------| | Native | `extern "c"` | C ABI (LLVM targets) | | JavaScript | `extern "js"` | WebAssembly/Browser | All FFI calls require the `FFI` capability. ## 26.2 Native FFI (C ABI) ### 26.2.1 Declaration syntax ```ori extern "c" from "m" { @_sin (x: float) -> float as "sin" @_sqrt (x: float) -> float as "sqrt" } ``` The `from` clause specifies the library name. The `as` clause maps Ori function names to C function names. ### 26.2.2 Library specification | Syntax | Meaning | |--------|---------| | `from "m"` | System library (libm) | | `from "/usr/lib/libfoo.so"` | Absolute path | | `from "./native/libcustom.so"` | Relative to project | | `from "libc"` | Header-only/inline | ### 26.2.3 Name mapping When C function names differ from desired Ori names: ```ori extern "c" from "m" { @abs (value: float) -> float as "fabs" @ln (x: float) -> float as "log" } ``` Without `as`, the Ori function name (without `@`) is used as the C name. ### 26.2.4 Visibility External declarations are private by default: ```ori // Private extern "c" from "m" { @sin (x: float) -> float } // Public pub extern "c" from "m" { @sin (x: float) -> float } ``` ## 26.3 JavaScript FFI (WASM target) ### 26.3.1 Declaration syntax ```ori extern "js" { @_sin (x: float) -> float as "Math.sin" @_sqrt (x: float) -> float as "Math.sqrt" @_now () -> float as "Date.now" } ``` ### 26.3.2 Module imports ```ori extern "js" from "./utils.js" { @_formatDate (timestamp: int) -> str as "formatDate" } ``` ### 26.3.3 Async functions Async JavaScript functions return `JsPromise`: ```ori extern "js" { @_fetch (url: str) -> JsPromise as "fetch" } ``` See [JsPromise Type](#2648-jspromise-type) for resolution semantics. ## 26.4 Type marshalling ### 26.4.1 Primitive types | Ori Type | C Type | WASM Type | JS Type | |----------|--------|-----------|---------| | `int` | `int64_t` | `i64` | `BigInt` or `number` | | `float` | `double` | `f64` | `number` | | `bool` | `bool` | `i32` | `boolean` | | `byte` | `uint8_t` | `i32` | `number` | JavaScript `number` has 53-bit integer precision. Large `int` values use `BigInt`. ### 26.4.2 C type aliases | Ori Type | C Type | Size | |----------|--------|------| | `c_char` | `char` | 1 byte | | `c_short` | `short` | 2 bytes | | `c_int` | `int` | 4 bytes | | `c_long` | `long` | platform | | `c_longlong` | `long long` | 8 bytes | | `c_float` | `float` | 4 bytes | | `c_double` | `double` | 8 bytes | | `c_size` | `size_t` | platform | ### 26.4.3 Strings Strings are copied at FFI boundaries: - **Native**: Ori string converted to null-terminated C string (allocated, copied) - **WASM**: Ori string converted via TextEncoder/TextDecoder ### 26.4.4 CPtr type `CPtr` represents an opaque pointer to C data structures: ```ori extern "c" from "sqlite3" { @sqlite3_open (filename: str, db: CPtr) -> int @sqlite3_close (db: CPtr) -> int } ``` `CPtr` cannot be dereferenced in Ori code. ### 26.4.5 Nullable pointers `Option` represents nullable pointers: ```ori extern "c" from "foo" { @get_resource (id: int) -> Option } ``` Returns `None` when the C function returns `NULL`. ### 26.4.6 Byte arrays ```ori extern "c" from "z" { @compress ( dest: [byte], dest_len: int, source: [byte], source_len: int ) -> int } ``` - `[byte]` as input: Pointer to data, length passed separately - `[byte]` as output: Pre-allocated buffer, modified in place - Bounds checking occurs on the Ori side before the call ### 26.4.7 JsValue type `JsValue` represents an opaque handle to a JavaScript object: ```ori extern "js" { @_document_query (selector: str) -> JsValue as "document.querySelector" @_element_set_text (elem: JsValue, text: str) -> void @_drop_js_value (handle: JsValue) -> void } ``` `JsValue` handles are reference counted in a heap slab and shall be explicitly dropped. ### 26.4.8 JsPromise type `JsPromise` represents a JavaScript Promise: ```ori extern "js" { @_fetch (url: str) -> JsPromise as "fetch" @_response_text (resp: JsValue) -> JsPromise } ``` **Implicit Resolution:** `JsPromise` is implicitly resolved at binding sites in functions with `uses Suspend`: ```ori @fetch_text (url: str) -> str uses Suspend, FFI = { let response = _fetch(url: url), // JsPromise resolved let text = _response_text(resp: response), // JsPromise resolved text } ``` **Semantics:** 1. When `JsPromise` is assigned to a binding or used where `T` is expected, the compiler inserts suspension/resolution 2. Resolution only occurs in functions with `uses Suspend` capability 3. Assigning `JsPromise` in a non-async context is a compile-time error ### 26.4.9 C structs The `#repr` attribute controls struct memory layout. It applies only to struct types. | Attribute | Effect | |-----------|--------| | `#repr("c")` | C-compatible field layout and alignment | | `#repr("packed")` | No padding between fields | | `#repr("transparent")` | Same layout as single field | | `#repr("aligned", N)` | Minimum N-byte alignment (N shall be power of two) | ```ori #repr("c") type CTimeSpec = { tv_sec: c_long, tv_nsec: c_long } #repr("packed") type PacketHeader = { version: byte, flags: byte, length: c_short } #repr("transparent") type FileHandle = { fd: c_int } #repr("aligned", 64) type CacheAligned = { value: int } ``` **Combining attributes:** `#repr("c")` may combine with `#repr("aligned", N)`. Other combinations are invalid: ```ori // Valid #repr("c") #repr("aligned", 16) type CAligned = { x: int, y: int } // Invalid - packed and aligned are contradictory #repr("packed") #repr("aligned", 16) // Error type Invalid = { x: int } ``` **Newtypes:** Newtypes (`type T = U`) are implicitly transparent — they have identical layout to their underlying type without requiring an explicit attribute. ```ori extern "c" from "libc" { @_clock_gettime (clock_id: int, ts: CTimeSpec) -> int as "clock_gettime" } ``` ### 26.4.10 Callbacks Ori functions can be passed to C as callbacks: ```ori extern "c" from "libc" { @qsort ( base: [byte], nmemb: int, size: int, compar: (CPtr, CPtr) -> int ) -> void } ``` ### 26.4.11 C variadics C variadic functions are supported with untyped variadic parameters: ```ori extern "c" { @printf (fmt: CPtr, ...) -> c_int } ``` Calling C variadic functions requires `unsafe`. ## 26.5 Unsafe expressions > **Proposal:** [unsafe-semantics-proposal.md](../../proposals/approved/unsafe-semantics-proposal.md) Operations that bypass Ori's safety guarantees require the `Unsafe` capability. The `unsafe { }` block discharges this capability locally: ```ori @raw_memory_access (ptr: CPtr, offset: int) -> byte uses FFI = unsafe { ptr_read_byte(ptr: ptr, offset: offset) }; ``` `Unsafe` is a _marker capability_ — it cannot be bound via `with...in` (E1203). A function that wraps unsafe operations in `unsafe { }` blocks does not propagate `Unsafe` to callers. See [Capabilities § Marker Capabilities](20-capabilities.md#2092-marker-capabilities). ### 26.5.1 Operations requiring unsafe - Dereference raw pointers - Pointer arithmetic - Access mutable statics - Transmute types - Call C variadic functions ### 26.5.2 Safe FFI calls Regular FFI calls (via `extern` declarations) are safe to call but require the `FFI` capability. Only operations Ori cannot verify require `unsafe`. The `FFI` capability tracks provenance (foreign code); the `Unsafe` capability tracks trust (safety bypasses). ## 26.6 FFI capability All FFI calls require the `FFI` capability: ```ori @call_c_function () -> int uses FFI = some_c_function(); @manipulate_dom () -> void uses FFI = { let elem = document_query(selector: "#app"); element_set_text(elem: elem, text: "Hello"); drop_js_value(handle: elem) } ``` Standard library functions internally use FFI but expose clean Ori APIs without requiring the `FFI` capability from callers. ## 26.7 Compile-time errors The `compile_error` built-in triggers a compile-time error: ```ori #target(arch: "wasm32") compile_error("std.fs is not available for WASM"); #target(not_arch: "wasm32") pub use "./read" { read, read_bytes }; ``` **Semantics:** - Evaluated during conditional compilation - Only triggers if the code path is active - Useful for platform availability errors ## 26.8 Error handling ### 26.8.1 Native FFI errors C functions typically return error codes. Deep FFI provides declarative error protocols that automate error checking and `Result` wrapping: ```ori // Declarative: #error(errno) generates Result wrapping automatically extern "c" from "libc" #error(errno) { @open (path: str, flags: c_int, mode: c_int) -> c_int as "open" } // Effective Ori signature: @open (...) -> Result uses FFI("libc") let fd = open(path: "/etc/hostname", flags: O_RDONLY, mode: 0)? ``` Available error protocols: `#error(errno)`, `#error(nonzero)`, `#error(null)`, `#error(negative)`, `#error(success: N)`, `#error(none)`. See [Deep FFI proposal](../../proposals/approved/deep-ffi-proposal.md) for full specification. Manual wrapping remains supported for custom error handling: ```ori extern "c" from "libc" { @_open (path: str, flags: c_int, mode: c_int) -> c_int as "open" } pub @open_file (path: str) -> Result uses FFI = { let fd = _open(path: path, flags: 0, mode: 0); if fd < 0 then Err(errno_to_error()) else Ok(fd) } ``` ### 26.8.2 WASM FFI errors JavaScript exceptions become Ori errors: ```ori extern "js" { @_json_parse (s: str) -> Result as "JSON.parse" } ``` If JavaScript throws, the function returns `Err` with the exception message. ## 26.9 Memory management ### 26.9.1 Native Ori's ARC handles Ori objects. For C objects, Deep FFI provides ownership annotations that integrate with ARC: - `owned CPtr` returns with `#free(fn)`: The compiler generates a `Drop` impl that calls the specified cleanup function. The value is tracked by ARC like any Ori value. - `borrowed CPtr` returns: Ori does not free the pointer. For `borrowed str`, Ori copies the string immediately. - Unannotated `CPtr`: Follows C conventions (untracked). Deep FFI phases enforcement from optional to warning to error. See [Deep FFI proposal](../../proposals/approved/deep-ffi-proposal.md) for the full ownership model. ### 26.9.2 WASM - **Linear memory**: Ori allocates from WASM linear memory - **JS object handles**: Reference counted in a heap slab; shall be explicitly dropped ```ori @use_js_object () -> void uses FFI = { let elem = document_query(selector: "#app"); element_set_text(elem: elem, text: "Hello"); drop_js_value(handle: elem) } ``` ## 26.10 Platform-specific declarations Use conditional compilation to provide platform-specific FFI: ```ori #target(not_arch: "wasm32") extern "c" from "m" { @_sin (x: float) -> float as "sin" } #target(arch: "wasm32") extern "js" { @_sin (x: float) -> float as "Math.sin" } // Public API works on both platforms pub @sin (angle: float) -> float = _sin(x: angle); ``` See [Conditional Compilation](25-conditional-compilation.md) for attribute syntax. ## 26.11 Deep FFI extensions > **Proposal:** [deep-ffi-proposal.md](../../proposals/approved/deep-ffi-proposal.md) Deep FFI is a set of opt-in annotations that layer on top of the extern declaration syntax. All existing FFI code continues to work unchanged. ### 26.11.1 Parameter modifiers > **Grammar:** See [grammar.ebnf](grammar.md) § FFI (`param_modifier`) Two modifiers for extern parameters: | Modifier | Meaning | Compiler Action | |----------|---------|-----------------| | `out` | C writes to this address; value folded into return type | Allocate stack slot, pass address, extract value after call | | `mut` | C may modify this buffer in place | Pass pointer to existing buffer | ```ori extern "c" from "sqlite3" #error(nonzero) { @sqlite3_open (filename: str, db: out owned CPtr) -> c_int } // Effective Ori signature: // @sqlite3_open (filename: str) -> Result uses FFI("sqlite3") ``` ### 26.11.2 Ownership annotations > **Grammar:** See [grammar.ebnf](grammar.md) § FFI (`ownership`) Two keywords — `owned` and `borrowed` — specify memory ownership transfer at the FFI boundary. | Annotation | On Return Type | On Parameter | |------------|---------------|--------------| | `owned` | Ori takes ownership; cleanup via `#free` | Ori transfers ownership to C | | `borrowed` | Ori copies immediately (str, [byte]) or non-owning view (CPtr) | C borrows temporarily | | _(none)_ | **str: borrowed** (safe default). Primitives: by value. CPtr: warning → error | Primitives: by value. CPtr: borrowed by default | The `#free(fn)` attribute specifies a cleanup function for `owned CPtr` returns. The compiler generates a `Drop` impl that calls this function when the value goes out of scope: ```ori extern "c" from "sqlite3" #free(sqlite3_close) #error(nonzero) { @sqlite3_open (filename: str, db: out owned CPtr) -> c_int @sqlite3_close (db: owned CPtr) -> c_int } ``` ### 26.11.3 Error protocol mapping A block-level `#error(...)` attribute specifies how C return values map to `Result`: | Protocol | Attribute | Success Condition | |----------|-----------|-------------------| | POSIX errno | `#error(errno)` | Return >= 0 | | Non-zero is error | `#error(nonzero)` | Return = 0 | | Negative is error | `#error(negative)` | Return >= 0 | | NULL is error | `#error(null)` | Return != NULL | | Specific success | `#error(success: N)` | Return = N | | No protocol | `#error(none)` | (no check) | Per-function `#error(...)` overrides the block default. `FfiError` is defined in `std.ffi`. ### 26.11.4 Parametric FFI capability `FFI` is a _parametric capability_. Each extern block's `from "..."` clause defines a distinct capability namespace: ```ori @query (path: str) -> Result uses FFI("sqlite3") = ... ``` Unparameterized `uses FFI` is shorthand for all FFI capabilities (backward compatible). ### 26.11.5 Capability-gated testability Each extern block generates a compiler-internal trait. The `with FFI("lib") = handler { ... } in` construct redirects extern calls to mock implementations: ```ori @test tests query { with FFI("sqlite3") = handler { sqlite3_open: (filename: str) -> Result = Ok(CPtr.null()), } in { test_database_logic() } } ``` The stateless `handler { ... }` form (without state) is syntactic sugar for `handler(state: ()) { ... }` from the stateful-mock-testing system. Unmocked functions fall through to the real C implementation. --- --- title: "Reflection" description: "Clause 27: Ori Language Specification — Reflection" order: 27 section: "Language" --- > **SUPERSEDED (2026-03-26):** The runtime reflection model described in this clause (`Reflect` trait, `TypeInfo`, `Unknown`, `#derive(Reflect)`) has been superseded by compile-time structural reflection. See `proposals/approved/compile-time-reflection-proposal.md`. This clause will be rewritten to describe the compile-time model (`fields_of(T)`, `variants_of(T)`, `$for`, `$if`, splice access). # 27 Reflection Reflection enables runtime type introspection for types that opt in via the `Reflect` trait. ## 27.1 Overview Ori provides read-only reflection with these key components: | Component | Purpose | |-----------|---------| | `Reflect` trait | Opt-in type introspection | | `TypeInfo` | Static type metadata | | `Unknown` | Type-erased container with safe downcasting | Reflection is opt-in. Types shall explicitly derive `Reflect` to enable runtime introspection. ## 27.2 Reflect trait ```ori trait Reflect { @type_info (self) -> TypeInfo; @field_count (self) -> int; @field_by_index (self, index: int) -> Option; @field_by_name (self, name: str) -> Option; @current_variant (self) -> Option; } ``` ### 27.2.1 Derivation ```ori #derive(Reflect) type Person = { name: str, age: int, email: Option, } ``` **Constraints:** - All fields shall implement `Reflect` - Private fields (`::`-prefixed) are excluded from reflection - Generic types derive conditionally: `Container` reflects when `T: Reflect` ## 27.3 TypeInfo structure ```ori type TypeInfo = { name: str, // Simple type name: "Person" module: str, // Full module path: "myapp.models" kind: TypeKind, // Category of type fields: [FieldInfo], // For structs (empty for others) variants: [VariantInfo], // For enums (empty for others) type_params: [str], // Generic parameter names: ["T", "E"] } type TypeKind = | Struct | Enum | Primitive | List | Map | Tuple | Function | Trait; type FieldInfo = { name: str, type_name: str, // Type as string: "Option" index: int, // 0-based position is_optional: bool, // True if type is Option } type VariantInfo = { name: str, // Variant name: "Some", "None" index: int, // Variant index fields: [FieldInfo], // Payload fields (empty for unit variants) } ``` TypeInfo is generated at compile time and stored in static tables. Each reflecting type has exactly one TypeInfo instance. ### 27.3.1 Accessing type information ```ori let person = Person { name: "Alice", age: 30, email: None }; let info = person.type_info(); assert_eq(actual: info.name, expected: "Person"); assert_eq(actual: info.kind, expected: Struct); assert_eq(actual: len(collection: info.fields), expected: 3) ``` ## 27.4 Unknown type `Unknown` is a type-erased container with safe downcasting: ```ori impl Unknown { @new (value: T) -> Unknown; @type_name (self) -> str; @type_info (self) -> TypeInfo; @is (self) -> bool; @downcast (self) -> Option; @unwrap (self) -> T; @unwrap_or (self, default: T) -> T; } ``` ### 27.4.1 Usage ```ori let value: Unknown = Unknown.new(value: 42); assert(condition: value.is()); assert_eq(actual: value.type_name(), expected: "int"); match value.downcast() { Some(n) -> print(msg: `Got integer: {n}`), None -> print(msg: "Not an integer"), } ``` Operations on `Unknown` values require explicit downcasting. Methods and fields cannot be accessed directly on `Unknown`. ## 27.5 Standard implementations ### 27.5.1 Primitives All primitives implement `Reflect`: - `int`, `float`, `str`, `bool`, `char`, `byte`, `void` - `Duration`, `Size` ### 27.5.2 Collections Collections implement `Reflect` when their element types implement `Reflect`: | Type | Constraint | |------|------------| | `[T]` | `T: Reflect` | | `{K: V}` | `K: Reflect, V: Reflect` | | `Set` | `T: Reflect` | | `Option` | `T: Reflect` | | `Result` | `T: Reflect, E: Reflect` | | `(A, B, ...)` | All elements: `Reflect` | ## 27.6 Derived implementation For a struct: ```ori #derive(Reflect) type Point = { x: int, y: int } ``` The compiler generates: ```ori impl Point: Reflect { @type_info (self) -> TypeInfo = $POINT_TYPE_INFO; @field_count (self) -> int = 2; @field_by_index (self, index: int) -> Option = match index { 0 -> Some(Unknown.new(value: self.x)), 1 -> Some(Unknown.new(value: self.y)), _ -> None, } @field_by_name (self, name: str) -> Option = match name { "x" -> Some(Unknown.new(value: self.x)), "y" -> Some(Unknown.new(value: self.y)), _ -> None, } @current_variant (self) -> Option = None; } ``` ### 27.6.1 Enum reflection For sum types, `current_variant` returns the active variant: ```ori #derive(Reflect) type Shape = | Circle(radius: float) | Rectangle(width: float, height: float) | Point; impl Shape: Reflect { @current_variant (self) -> Option = Some(match self { Circle(_) -> VariantInfo { name: "Circle", index: 0, fields: [...] }, Rectangle(_, _) -> VariantInfo { name: "Rectangle", index: 1, fields: [...] }, Point -> VariantInfo { name: "Point", index: 2, fields: [] }, }); // ... } ``` ## 27.7 Generic reflection Generic types derive `Reflect` conditionally: ```ori #derive(Reflect) type Container = { items: [T], count: int } // Reflects when T: Reflect impl Container: Reflect { @type_info (self) -> TypeInfo = TypeInfo { ...base_info, type_params: [T.type_info().name], } // ... } ``` ## 27.8 Field iteration ```ori extend T { @fields (self) -> impl Iterator where Item == (str, Unknown); } ``` Usage: ```ori let person = Person { name: "Alice", age: 30, email: None }; for (name, value) in person.fields() do print(msg: `{name}: {value.type_name()}`); ``` ## 27.9 Constraints ### 27.9.1 Read-only Reflection is read-only. Values cannot be modified through reflection. ### 27.9.2 Public fields only Private fields (`::`-prefixed) are not visible to reflection. ### 27.9.3 No method reflection Method reflection is not supported. Only field access is available. ### 27.9.4 Object safety The `Reflect` trait methods return concrete types, making them individually object-safe. However, `Reflect` is not practically usable as a trait object because derivation requires the concrete type at compile time. ## 27.10 Performance ### 27.10.1 Static metadata - TypeInfo generated at compile time - No per-instance overhead - O(1) access to type information ### 27.10.2 Field access - By index: O(1) via match dispatch - By name: O(1) via static hash map ### 27.10.3 Unknown boxing Creating `Unknown` requires one allocation for the erased value (reference counted). ### 27.10.4 Opt-out Types that do not derive `Reflect` have zero reflection cost. ## 27.11 Error codes | Code | Description | |------|-------------| | E0450 | Cannot derive `Reflect`: field type does not implement `Reflect` | | E0451 | Type mismatch in downcast | --- --- title: "Annex C — Built-in functions" description: "Ori Language Specification — Annex C (normative)" order: 102 section: "Annexes" --- # Annex C (normative) — Built-in functions This annex defines the functions, methods, and types provided by the standard prelude. A conforming implementation shall provide all functions defined in this annex. Core functions provided by the language. All built-in functions require named arguments, except type conversions. ## Reserved Names Built-in names cannot be used for function definitions. Reserved in call position only; may be used as variables. ```ori let min = 5; // OK: variable min(left: a, right: b) // OK: calls built-in @min (...) = ... // error: reserved name ``` ## Type Conversions (function_val) Type conversions are the sole exception to named argument requirements. Positional syntax is allowed. | Function | From | Behavior | |----------|------|----------| | `int(x)` | `float` | Truncates toward zero; panics on NaN, ±Inf, or out of i64 range | | | `str` | Parses decimal integer; panics on invalid (see parsing rules below) | | | `bool` | `true`→1, `false`→0 | | | `byte` | Zero-extends | | `float(x)` | `int` | Exact for values within 2^53; precision loss beyond (inherent to i64→f64) | | | `str` | Parses float; panics on invalid (see parsing rules below) | | `str(x)` | `int`, `float`, `bool` | Decimal representation | | `byte(x)` | `int` | Truncates to 8 bits | | | `str` | First UTF-8 byte | **Parsing rules for `int(str)`:** The string shall consist of an optional leading sign (`+` or `-`) followed by one or more ASCII decimal digits (`0`–`9`). No whitespace, digit separators (`_`), or base prefixes (`0x`, `0b`) are permitted. The resulting value shall be in the range of `int` (−2^63 to 2^63 − 1). Any string not matching this format causes a panic. EXAMPLE `int(x: "42")` → `42`. `int(x: "-7")` → `-7`. `int(x: " 42")` → panic (leading whitespace). `int(x: "0xFF")` → panic (not decimal). **Parsing rules for `float(str)`:** The string shall be a valid decimal floating-point representation: optional sign, decimal digits, optional decimal point with fractional digits, optional exponent (`e` or `E` with optional sign and digits). The strings `"inf"`, `"-inf"`, `"NaN"` (case-sensitive) are also accepted. No whitespace or digit separators are permitted. Any string not matching this format causes a panic. EXAMPLE `float(x: "3.14")` → `3.14`. `float(x: "2.5e-8")` → `2.5e-8`. `float(x: "inf")` → positive infinity. `float(x: "hello")` → panic. ## Collection Functions ``` len(collection: T) -> int is_empty(collection: T) -> bool ``` `len(collection:)` desugars to `collection.len()`. The result is always ≥ 0. Complexity is O(1) for all built-in types. Built-in `Len` implementations: | Type | Returns | |------|---------| | `[T]` | Number of elements | | `{K: V}` | Number of entries | | `str` | Number of UTF-8 bytes (not codepoints) | | `Set` | Number of elements | | `Range` | Number of values in range | | Tuples | Number of elements (compile-time) | NOTE For string codepoint count, use `.chars().count()`. Infinite ranges do not implement `Len`. `is_empty(collection:)` desugars to `collection.is_empty()`. Equivalent to `len(collection:) == 0` for types implementing both `Len` and `IsEmpty`. ## Option Functions ``` is_some(opt: Option) -> bool is_none(opt: Option) -> bool ``` ### Option Methods ``` Option.map(transform: T -> U) -> Option Option.unwrap_or(default: T) -> T Option.ok_or(err: E) -> Result Option.and_then(then: T -> Option) -> Option Option.filter(predicate: T -> bool) -> Option ``` ## Result Functions ``` is_ok(result: Result) -> bool is_err(result: Result) -> bool ``` ### Result Methods ``` Result.map(transform: T -> U) -> Result Result.map_err(transform: E -> F) -> Result Result.unwrap_or(default: T) -> T Result.ok() -> Option Result.err() -> Option Result.and_then(then: T -> Result) -> Result ``` ## Assertions All assertion functions are available without import (`use std.testing { ... }` is required for test files that are not attached tests). ``` assert(condition: bool) -> void ``` Panics with "assertion failed" when `condition` is `false`. No capability required. ``` assert_eq(actual: T, expected: T) -> void assert_ne(actual: T, unexpected: T) -> void ``` `assert_eq` panics when `actual != expected`. `assert_ne` panics when `actual == unexpected`. Panic messages include the `Debug` representation of both values. NOTE `assert_eq(actual: float.nan, expected: float.nan)` panics because NaN is not equal to itself per IEEE 754. ``` assert_some(option: Option) -> T assert_none(option: Option) -> void assert_ok(result: Result) -> T assert_err(result: Result) -> E ``` `assert_some` and `assert_ok` return the inner value on success. `assert_err` returns the error value. All panic when the assertion fails. ``` assert_panics(expr: () -> void) -> void assert_panics_with(expr: () -> void, message: str) -> void ``` `assert_panics` evaluates the closure `expr` and panics if `expr` does _not_ panic. `assert_panics_with` additionally verifies that the panic message contains `message` as a substring. ## Comparison ``` compare(left: T, right: T) -> Ordering ``` Desugars to `left.compare(other: right)`. Returns `Less`, `Equal`, or `Greater`. ``` min(left: T, right: T) -> T max(left: T, right: T) -> T ``` Returns the lesser (or greater) of two values. When values are equal, returns `left` (the first argument). For `float`, NaN is considered greater than all other values (per `Comparable` trait contract). ``` hash_combine(seed: int, value: int) -> int ``` Combines two hash values using the Boost hash_combine algorithm. Pure function with no side effects. ## I/O ``` print(msg: str) -> void ``` Writes `msg` to stdout followed by a newline. Requires the `Print` capability (available by default). Inside an `@panic` handler, writes to stderr instead. ## Control ``` panic(msg: str) -> Never ``` Unconditionally panics with the given message. Never returns. Triggers the `@panic` handler if defined. See [23.4](23-program-execution.md#234-panic-handler). ## Panic Handler An optional top-level function that executes before program termination when a panic occurs. ```ori @panic (info: PanicInfo) -> void = { print(msg: `Fatal error: {info.message}`); print(msg: `Location: {info.location.file}:{info.location.line}`); } ``` ### Rules - At most one `@panic` function per program - Shall have signature `(PanicInfo) -> void` - Executes synchronously before program exit - If `@panic` itself panics, immediate termination (no recursion) ### PanicInfo Type ```ori type PanicInfo = { message: str, location: TraceEntry, stack_trace: [TraceEntry], thread_id: Option, } ``` The `location` field uses `TraceEntry` (which has `function`, `file`, `line`, `column`). The `stack_trace` is ordered from most recent to oldest. The `thread_id` is `Some(id)` in concurrent contexts, `None` for single-threaded execution. ### Implicit Stderr Inside `@panic`, the `print()` function writes to stderr instead of stdout. ### Concurrent Panics When multiple tasks panic simultaneously: 1. The first panic to reach the handler wins 2. Subsequent panics are recorded but do not re-run the handler 3. After the handler completes, the program exits with non-zero code ### Default Handler If no `@panic` handler is defined: ```ori @panic (info: PanicInfo) -> void = { print(msg: `panic: {info.message}`); print(msg: ` at {info.location.file}:{info.location.line}`); for frame in info.stack_trace do print(msg: ` {frame.function}`); } ``` ### Capabilities Any capability may be declared. Capabilities that perform I/O (Http, FileSystem) may hang or fail, risking the handler never completing. Simple handlers (stderr logging) are safest. ## Developer Functions ### todo ``` todo() -> Never todo(reason: str) -> Never ``` Marks unfinished code. Panics with "not yet implemented" and file location. ```ori @parse_json (input: str) -> Result = todo(); // Panics: "not yet implemented at src/parser.ori:15" @handle_event (event: Event) -> void = match event { Click(pos) -> handle_click(pos: pos), Scroll(delta) -> todo(reason: "scroll handling"), // Panics: "not yet implemented: scroll handling at src/ui.ori:42" KeyPress(key) -> handle_key(key: key), } ``` ### unreachable ``` unreachable() -> Never unreachable(reason: str) -> Never ``` Marks code that should never execute. Panics with "unreachable code reached" and file location. ```ori @day_type (day: int) -> str = match day { 1 | 2 | 3 | 4 | 5 -> "weekday", 6 | 7 -> "weekend", _ -> unreachable(reason: "day must be 1-7"), } ``` ### dbg ``` dbg(value: T) -> T dbg(value: T, label: str) -> T ``` Prints value with location to stderr, returns value unchanged. Requires `Debug` trait. Uses `Print` capability. ```ori let x = dbg(value: calculate()); // Prints: [src/math.ori:10] = 42 let y = dbg(value: get_y(), label: "y coordinate"); // Prints: [src/point.ori:6] y coordinate = 200 ``` Output format: - Without label: `[file:line] = value` - With label: `[file:line] label = value` ## Concurrency ``` is_cancelled() -> bool ``` Available in async contexts. Returns `true` if the current task has been marked for cancellation. See [Patterns § nursery](15-patterns.md#1544-nursery) for cancellation semantics. ## Resource Management ### drop_early ``` drop_early(value: T) -> void ``` Forces a value to be dropped before the end of its scope. Takes ownership of the value, causing its destructor to run immediately (if the type implements `Drop`) and its memory to be reclaimed. ```ori { let file = open_file(path: "data.txt"); let content = read_all(file); drop_early(value: file); // Close immediately, don't wait for scope end process(content); // Continue with content, file already closed } ``` Works for any type, not just types implementing `Drop`: - For types with `Drop`: the `drop` method is called, then memory is reclaimed - For types without `Drop`: memory is reclaimed immediately Use `drop_early` to release resources (file handles, connections, locks) as soon as they are no longer needed, rather than waiting for scope exit. ## Iteration ### repeat ``` repeat(value: T) -> impl Iterator ``` Creates an infinite iterator that yields clones of `value`. The type `T` shall implement `Clone`. ```ori repeat(value: 0).take(count: 5).collect() // [0, 0, 0, 0, 0] repeat(value: "x").take(count: 3).collect() // ["x", "x", "x"] ``` Infinite iterators shall be bounded before terminal operations: ```ori repeat(value: 1).collect() // Infinite loop, eventual OOM repeat(value: default).take(count: n).collect() // Safe ``` Common patterns: ```ori // Initialize list let zeros: [int] = repeat(value: 0).take(count: 100).collect(); // Zip with constant items.iter().zip(other: repeat(value: multiplier)).map(transform: (x, m) -> x * m) ``` ## Compile-Time ### compile_error ``` compile_error(msg: str) -> Never ``` Causes a compile-time error with the given message. Valid only in contexts that are statically evaluable at compile time: 1. **Conditional compilation branches**: Inside `#target(...)` or `#cfg(...)` blocks 2. **Const-if branches**: Inside `if $constant then ... else ...` where the condition involves compile-time constants ```ori // OK: compile_error in conditional block #target(os: "windows") @platform_specific () -> void = compile_error(msg: "Windows not supported"); // OK: compile_error in dead branch @check () -> void = if $target_os == "windows" then compile_error(msg: "Windows not supported") else (); // ERROR: compile_error in unconditional code @bad () -> void = compile_error(msg: "always fails"); ``` See [Conditional Compilation](25-conditional-compilation.md) for conditional compilation semantics. ### embed ``` embed(path_expr) -> str | [byte] ``` Embeds the contents of a file at compile time. The path shall be a const-evaluable `str` expression, resolved relative to the source file containing the `embed` expression. The return type is determined by the expected type in context: - If `str` is expected, the file shall be valid UTF-8. A compile-time error is produced if the file contains invalid UTF-8 sequences. - If `[byte]` is expected, the file is read as raw bytes with no encoding validation. If the expected type cannot be inferred, it is an error. The compiler shall require an explicit type annotation. ```ori // UTF-8 text embedding let $SCHEMA: str = embed("schema.sql"); // Binary embedding let $ICON: [byte] = embed("assets/icon.png"); // Const expression paths (not limited to literals) let $DATA_DIR = "data"; let $CONFIG: str = embed(`{$DATA_DIR}/config.toml`); ``` **Path restrictions:** - Absolute paths are an error. - Paths that resolve outside the project root (via `..`) are an error. - Path separators shall be `/` (normalized by the compiler across platforms). **Size limit:** The default maximum embedded file size is 10 MB. This limit may be overridden per-expression with `#embed_limit(size:)` or project-wide in `ori.toml`. **Dependency tracking:** The compiler shall track embedded files as build dependencies. Modifications to an embedded file shall trigger recompilation of the module containing the `embed` expression. ### has_embed ``` has_embed(path_expr) -> bool ``` Compile-time boolean expression. Evaluates to `true` if the file at `path_expr` exists and is readable, `false` otherwise. The path is resolved with the same rules as `embed`. ```ori let $HELP: str = if has_embed("HELP.md") then embed("HELP.md") else "No help available"; ``` **Dependency tracking:** The compiler shall track files referenced by `has_embed`. A change in file existence shall trigger recompilation. ## Prelude Available without import: - `int`, `float`, `str`, `byte` - `len`, `is_empty` - `is_some`, `is_none`, `Some`, `None` - `is_ok`, `is_err`, `Ok`, `Err` - All assertions - `print`, `panic`, `compare`, `min`, `max` - `todo`, `unreachable`, `dbg` - `repeat`, `drop_early` - `compile_error` - `embed`, `has_embed` - `is_cancelled` (async contexts only) - `CancellationError`, `CancellationReason` types - `PanicInfo` type (with `message`, `location`, `stack_trace`, `thread_id`) ## Collection Methods Data transformation via method calls on collections. ### List Methods ``` [T].map(transform: T -> U) -> [U] [T].filter(predicate: T -> bool) -> [T] [T].fold(initial: U, op: (U, T) -> U) -> U [T].find(where: T -> bool) -> Option [T].any(predicate: T -> bool) -> bool [T].all(predicate: T -> bool) -> bool [T].first() -> Option [T].last() -> Option [T].take(n: int) -> [T] [T].skip(n: int) -> [T] [T].reverse() -> [T] [T].sort() -> [T] where T: Comparable [T].contains(value: T) -> bool where T: Eq ``` ### Range Methods ``` Range.map(transform: T -> U) -> [U] Range.filter(predicate: T -> bool) -> [T] Range.fold(initial: U, op: (U, T) -> U) -> U Range.collect() -> [T] Range.contains(value: T) -> bool ``` ### String Methods ``` str.split(sep: str) -> [str] str.trim() -> str str.to_uppercase() -> str str.to_lowercase() -> str str.starts_with(prefix: str) -> bool str.ends_with(suffix: str) -> bool str.contains(substr: str) -> bool ``` ## Standard Library | Module | Contents | |--------|----------| | `std.math` | `sqrt`, `abs`, `pow`, `sin`, `cos`, `tan` | | `std.string` | `split`, `join`, `trim`, `upper`, `lower` | | `std.list` | `sort`, `reverse`, `unique` | | `std.io` | File I/O | | `std.net` | Network | | `std.resilience` | `retry`, `exponential`, `linear` | | `std.validate` | `validate` | ### std.resilience ```ori use std.resilience { retry, exponential, linear }; retry(op: fetch(url), attempts: 3, backoff: exponential(base: 100ms)); retry(op: fetch(url), attempts: 5, backoff: linear(delay: 100ms)) ``` ### std.validate ```ori use std.validate { validate }; validate( rules: [ age >= 0 | "age must be non-negative", name != "" | "name required", ], value: User { name, age }, ) ``` Returns `Result`. --- --- title: "Annex D — Formatting" description: "Ori Language Specification — Annex D (informative)" order: 103 section: "Annexes" --- # Annex D (informative) — Formatting This annex defines the formatting conventions applied by the `ori fmt` tool. These conventions are informative; non-conformance to formatting does not affect program validity. Canonical source formatting. Zero-config, deterministic. The `ori fmt` command produces a single canonical format for any valid Ori source file. The core principle is **width-based breaking**: constructs remain inline if they fit within the line width limit, otherwise they break according to construct-specific rules. --- ## General Rules ### Indentation Four spaces per indentation level. Tabs are not permitted. ### Line Width 100 characters. This is the threshold for width-based breaking decisions. ### Trailing Commas Required in multi-line constructs. Forbidden in single-line constructs. ```ori // Single-line: no trailing comma let $p = Point { x: 1, y: 2 }; // Multi-line: trailing comma required let $config = Config { host: "localhost", port: 8080, debug: true, }; ``` ### Semicolons A semicolon terminates statements within blocks. The last expression in a block (the result expression) has no semicolon. A block where all expressions are terminated by semicolons produces `void`. At module level, declarations ending with `}` (block bodies, type definitions with struct bodies, trait/impl blocks) have no trailing semicolon. All other declarations end with `;`. ```ori // Block body: no ; after } @process () -> int = { let $x = get_value(); let $y = transform(x:); x + y } // Expression body: ; terminates @add (a: int, b: int) -> int = a + b; // Module-level: ; after non-block declarations let $MAX = 100; type UserId = int; ``` ### Blank Lines - One blank line between top-level declarations (functions, types, traits, impls) - One blank line after the imports block - One blank line after the constants block - One blank line between trait/impl methods, except in single-method blocks - No consecutive blank lines (collapsed to one) - User blank lines within constant blocks are preserved for semantic grouping ### Blank Line Before Result Expression In a block with two or more statements preceding the result expression, a blank line shall separate the last statement from the result expression. ```ori @compute (x: int) -> int = { let $a = step_one(x:); let $b = step_two(a:); a + b } ``` ### Whitespace Cleanup Consecutive blank lines are collapsed to a single blank line. Trailing whitespace on all lines is stripped. Files end with a single newline character. --- ## Spacing Rules | Context | Rule | Example | |---------|------|---------| | Binary operators | Space around | `a + b`, `x == y`, `a && b` | | Arrows | Space around | `x -> x + 1`, `-> Type` | | Colons (type annotations) | Space after | `x: int`, `key: value` | | Commas | Space after | `f(a, b, c)` | | Parentheses | No space inside | `f(x)`, `(a, b)` | | Brackets | No space inside | `[1, 2]`, `items[0]` | | Braces (all `{ }`) | Space inside | `Point { x, y }`, `{ a; b }` | | Empty delimiters | No space | `[]`, `{}`, `()` | | Field/member access `.` | No space | `point.x`, `std.math` | | Range operators `..`/`..=` | No space | `0..10`, `0..=100` | | Range step `by` | Space around | `0..100 by 5` | | Spread `...` | No space after | `[...a, ...b]`, `f(...args)` | | Unary operators | No space after | `-x`, `!valid`, `~mask` | | Error propagation `?` | No space before | `fetch()?` | | Pipe `\|>` | Space around | `x \|> f()` | | Pipe to method `\|> .m()` | Space around `\|>`, no space before `.` | `x \|> .trim()` | | Nullish coalescing `??` | Space around | `a ?? b` | | Labels `:` | No space around | `loop:outer`, `break:label` | | Type conversion `as`/`as?` | Space around | `42 as float`, `"42" as? int` | | Visibility `pub` | Space after | `pub @add`, `pub type` | | Generic bounds `:` | Space around | `T: Clone` | | Multi-trait `+` | Space around | `Printable + Debug` | | Default type params `=` | Space around | `` | | Default param values `=` | Space around | `port: int = 8080` | | Sum type variants `\|` | Space around | `Red \| Green \| Blue` | | Compound assignment | Space around | `x += 1`, `flags \|= FLAG` | | Comments `//` | Space after | `// comment` | | Punning `:` | No space after | `f(name:, age:)` | | Qualified path `::` | No space | `Type::Trait::Assoc` | | Import `without` | Space around | `Trait without def` | | Fixed-capacity `max` | Space around | `[T, max N]` | | FFI `out` | Space after | `db: out CPtr` | | FFI `owned`/`borrowed` | Space after | `owned CPtr`, `borrowed str` | --- ## Breaking Rules ### Width-Based Breaking Most constructs follow a single rule: **inline if the construct fits within 100 characters, break otherwise**. | Construct | Inline | Broken | |-----------|--------|--------| | Function parameters | All on one line | One per line, `)` on own line | | Function arguments | All on one line | One per line, `)` on own line | | Generic parameters | All on one line | One per line, `>` on own line | | Where constraints | After signature | New indented line, aligned | | Capabilities (`uses`) | After signature | New indented line | | Contracts (`pre`/`post`) | After signature | Each on own indented line | | Struct fields (definition) | All on one line | One per line | | Struct fields (literal) | All on one line | One per line | | Sum type variants | All on one line | One per line with leading `\|` | | Map entries | All on one line | One per line | | Tuple elements | All on one line | One per line | | Import items | All on one line | One per line, sorted | | Lists (simple items) | All on one line | Wrap (bin-pack) | | Lists (complex items) | All on one line | One per line | | Nested blocks | Inline | Stacked | | `if-then-else` | Inline | `else` on new indented line | | `for...do`/`yield` | Inline | Break after `do`/`yield` | | `loop { }` | Inline | Stacked | | `unsafe { }` | Inline | Stacked | | `with...in` | Inline | Break at `in` | | `timeout`/`cache`/`catch` | Inline | Stacked | | Lambdas | Inline | Block body `{ }` | | Method chains | Inline | Break at every `.` | | Pipe chains `\|>` | Inline | Each `\|>` on own line, same indent as receiver | | Binary expressions | Inline | Break before operator | | Destructuring patterns | Inline | One per line | | Capset declarations | Inline, sorted | One per line, sorted | | Conditional compilation attrs | Inline | One condition per line | | Complex type annotations | Inline | Break at outermost `<>` or `->` | | `impl Trait` with `where` | Inline | `where` on new indented line | ### Always-Stacked Constructs These constructs are always formatted in stacked (multi-line) form regardless of width: | Construct | Reason | |-----------|--------| | Function block body `= { }` | Top-level function bodies always stacked | | `try { }` | Error-propagating blocks emphasize sequential steps | | `match { }` | One arm per line aids pattern scanning | | `recurse()` | Named parameter pattern with lambda arguments | | `parallel()` / `spawn()` | Concurrency patterns with task lists | | `nursery()` | Structured concurrency pattern | ### Independent Breaking Nested constructs break independently based on their own width. An outer construct breaking does not force inner constructs to break. Each nested construct applies its own formatting rules. The formatter does not enforce a maximum nesting depth. Deep nesting is a code quality concern, not a formatting concern. --- ## Declarations ### Functions Expression body inline when the entire declaration fits within 100 characters. Block body always stacked. ```ori // Expression body — inline @add (a: int, b: int) -> int = a + b; // Block body — always stacked @process (input: str) -> Result = { let $data = parse(input:); let $result = transform(data:); Ok(result) } ``` Opening brace `{` appears on the same line as `=` (K&R/1TBS style). Closing `}` aligns with the declaration keyword (`@`, `trait`, `impl`, etc.). ### Parameters All parameters inline if the declaration fits within 100 characters. Otherwise, one parameter per line with `)` on its own line. Trailing comma in multi-line form. ```ori // Inline @connect (host: str, port: int = 8080) -> Connection = { ... } // Broken — one per line @configure ( host: str = "localhost", port: int = 443, timeout: Duration = 30s, retries: int = 3, ) -> Config = { ... } ``` Default parameter values use `= expr` with spaces around `=`. The default stays with its parameter when breaking. When a default expression is long, break after `=` and indent the default value: ```ori @configure ( host: str = "localhost", port: int = 8080, retry_policy: RetryPolicy = RetryPolicy.exponential(base: 100ms, max: 30s), ) -> Config = { ... } ``` Variadic parameters use `...` attached to the type with no space: `nums: ...int`. Variadic trait objects use the same pattern: `items: ...Printable`. ### Return Type The return type stays on the `)` line. The body breaks to the next line if the full declaration exceeds 100 characters. ```ori // Return type on ) line @long_name ( first: int, second: str, ) -> Result = compute(first:, second:); // Body breaks to next line when too long @long_name ( first: int, second: str, ) -> Result = compute_something_complex(input: data); ``` ### Generic Parameters Inline if the declaration fits. One per line otherwise. Uses `T: Trait` syntax for bounds. ```ori // Inline @identity (x: T) -> T = x; @sort (items: [T]) -> [T] = { ... } // Broken — one per line @complex< T: Comparable, U: Hashable, $N: int, $M: int, > (items: [T], keys: [U]) -> [[T]] where N > 0, M > 0 = { ... } ``` Const generic parameters use `$N: type` in generic brackets. Const bounds use `where N > 0` or compound expressions like `where N > 0 && N <= 100`. ### Where Clauses Inline if the declaration fits. Otherwise, `where` on a new indented line with constraints aligned. ```ori // Inline @sort (items: [T]) -> [T] where T: Comparable = { ... } // Broken — where on new line @process (items: [T], f: (T) -> U) -> [U] where T: Clone + Debug, U: Default + Printable = { ... } ``` ### Capabilities Inline if the declaration fits. Otherwise, on a new indented line, comma-separated. ```ori // Inline @fetch (url: str) -> Result uses Http = { ... } // Broken @complex_operation (input: Data) -> Result uses Http, FileSystem, Logger, Cache = { ... } ``` ### Contracts Inline if the declaration fits. Otherwise, each contract on its own indented line. Canonical order: `where` → `uses` → `pre` → `post`. ```ori // Inline @clamp (n: int, lo: int, hi: int) -> int pre(lo <= hi) = { ... } // Broken — each on own line @process (items: [T]) -> [T] where T: Clone uses FileSystem pre(!items.is_empty() | "items must not be empty") post(r -> r.len() <= items.len()) = { ... } // Multiple pre conditions @range_check (low: int, high: int, value: int) -> bool pre(low <= high | "low must not exceed high") pre(value >= 0 | "value must be non-negative") = value >= low && value <= high; ``` ### Visibility `pub` is a prefix with a space before the declaration keyword. ```ori pub @process (input: str) -> str = { ... } pub type Config = { ... }; pub let $VERSION = "1.0.0"; pub trait Serializable { ... } pub def impl Printable { ... } pub extend str { ... } ``` ### Pattern Parameters and Guards Each clause of a pattern-matched function is a separate declaration. Guards use `if` after parameters. Long guards break to an indented line. Clauses of the same function are separated by blank lines. ```ori @factorial (0: int) -> int = 1; @factorial (n: int) -> int = n * factorial(n - 1); @classify (n: int) -> str if n > 0 = "positive"; @classify (n: int) -> str if n < 0 = "negative"; @classify (_: int) -> str = "zero"; ``` Long guards break to a new indented line. Inside the guard, `&&` and `||` follow standard binary expression breaking (break before operator). The `=` follows after the guard. ```ori @validate (input: str) -> bool if input.len() > 0 && input.len() <= MAX_LENGTH && is_ascii(input:) = true; @validate (_: str) -> bool = false; ``` ### Const Functions Const functions use the `$` prefix instead of `@`. All other formatting rules are identical to regular functions. ```ori $factorial (n: int) -> int = if n <= 1 then 1 else n * factorial(n - 1); $fibonacci (n: int) -> int = { if n <= 1 then n else fibonacci(n - 1) + fibonacci(n - 2) } ``` ### Test Declarations Test attributes appear on their own line above. `tests @target` stays on the same line as the function name. Block body is always stacked. Test declaration order is preserved (no reordering). ```ori @test_add tests @add () -> void = { assert_eq(actual: add(a: 1, b: 2), expected: 3); } #skip("not yet implemented") @test_advanced tests @parse () -> void = { assert_eq(actual: parse(input: "42"), expected: 42); } #compile_fail("E0042") @test_bad_type tests @process () -> void = { process(input: 42); } // Multi-target test @test_both tests @parse tests @format () -> void = { let $parsed = parse(input: "42"); let $formatted = format(value: parsed); assert_eq(actual: formatted, expected: "42"); } ``` ### `@main` Entry Points `@main` follows all regular function formatting rules. No special treatment. ```ori @main (args: [str]) -> void uses Http, FileSystem = { let $data = fetch(url: args[0]); write_file(path: args[1], content: data); } ``` --- ## Type Definitions ### Structs Width-based. Fields one per line when broken. Trailing comma in multi-line form. ```ori // Inline type Point = { x: int, y: int }; // Broken type User = { id: int, name: str, email: str, created_at: Duration, }; ``` ### Structs with Bounds `where` clause appears before `=`. When `where` is present and breaks, `=` goes on its own line. ```ori type Wrapper = { value: T }; type SortedMap where K: Comparable + Hashable = { keys: [K], values: [V], size: int, } ``` ### Sum Types Inline if fits. When broken, variants use leading `|` on each line. Variant payloads break independently. ```ori // Inline type Color = Red | Green | Blue; // Broken — leading | type Shape = | Circle(radius: float) | Rectangle(width: float, height: float) | Triangle(a: float, b: float, c: float); // Complex payloads break independently type Event = | Click(x: int, y: int, button: MouseButton) | KeyPress(key: Key, modifiers: Set) | Resize( width: int, height: int, old_width: int, old_height: int, ) | Close; ``` ### Newtypes Newtype declarations are always inline. Derives appear on their own line above. ```ori type UserId = int; #derive(Eq, Clone, Debug, Hashable) type UserId = int; ``` ### Trait Object Types Trait object types are formatted as regular types. Multi-trait uses `+` with spaces around. Breaks at `+` when long. ```ori @display (item: Printable) -> str = item.to_str(); @debug_print (item: Printable + Debug) -> void = { ... } ``` ### Existential Types (`impl Trait`) Inline if fits. `where` on associated types breaks to a new indented line when long. ```ori // Inline @iter (self) -> impl Iterator where Item == int = { ... } // Broken — where on new line @pairs (self) -> impl Iterator where Item == (str, int) = { ... } ``` ### Fixed-Capacity List Types `[T, max N]` has space around `max`. The type is never broken internally. ```ori @buffer () -> [int, max 64] = [int, max 64](); type RingBuffer = { data: [T, max 256], head: int, tail: int, }; ``` ### Complex Type Annotations Inline if fits. Break at outermost `<>` or `->` first. Inner types break independently. ```ori // Inline let $handler: (int) -> Result = process; // Function type — break before -> let $processor: (Config, [UserData], {str: int}) -> Result<[ProcessedData], Error> = pipeline; // Deeply nested — break at outermost first let $data: Result< {str: [Option]}, ProcessingError, > = fetch_all(); // Multiple levels break independently let $complex: Result< {str: [ Option, ]}, Error, > = compute(); // Function type inside generic let $handlers: {str: (Request) -> Result} = build_routes(); ``` --- ## Trait and Impl Blocks ### Trait Bodies Opening `{` on same line. Canonical order within trait: associated types first, then required methods (no body), then default methods (with body). Blank line between each group. Single-method traits skip blank lines. ```ori trait Printable { @to_str (self) -> str } trait Collection { type Item; type Index = int; @get (self, index: Self.Index) -> Option @len (self) -> int @is_empty (self) -> bool = { self.len() == 0 } } ``` ### Impl Bodies Associated type assignments first, then methods in trait declaration order. Blank line between methods. ```ori impl Point: Printable { @to_str (self) -> str = `({self.x}, {self.y})`; } impl Range: Iterator { type Item = int; @next (self) -> (Option, Self) = { if self.current >= self.end then (None, self) else (Some(self.current), { ...self, current: self.current + 1 }) } } // Generic impl impl [T]: Printable { @to_str (self) -> str = { let $items = for item in self yield item.to_str(); "[" + items.join(separator: ", ") + "]" } } ``` ### Default Implementations `def` is a prefix like `pub`. Body formatting is identical to regular `impl` blocks. ```ori def impl Printable { @to_str (self) -> str = self.debug(); } pub def impl Comparable { @compare (self, other: Self) -> Ordering = { Ordering.Equal } } ``` ### Extensions `extend` blocks follow the same formatting as `impl` blocks. ```ori pub extend str { @words (self) -> [str] = self.split(separator: " "); @lines (self) -> [str] = self.split(separator: "\n"); } extend [T] { @join_str (self, separator: str) -> str = { for item in self yield item.to_str() } } ``` --- ## Expressions ### Blocks Function-body blocks are always stacked. Nested blocks (inside `let`, `for`, `if`, etc.) follow width-based breaking. ```ori // Function body — always stacked @compute () -> int = { let $x = 1; let $y = 2; x + y } // Nested block — inline if fits let $v = { let $x = 1; x + 2 }; // Nested block — stacked when long let $result = { let $x = compute(); let $y = transform(x:); x + y }; ``` Void blocks (all expressions terminated by semicolons) have no result expression. The blank-line-before-result rule does not apply since there is no result to separate. ```ori // Void block — no result, no blank line for item in items do { validate(item:); process(item:); log(msg: `processed {item}`); }; ``` ### Let Bindings with Type Annotations When a `let` binding with a type annotation exceeds width, break after `=` first (keeps name and type together). The type annotation itself breaks only if name + type alone exceeds width, using the complex type annotation rules. ```ori // Inline let $x: int = 42; // Value breaks after = let $config: Result = load_config(path: "settings.json"); // Type itself breaks (very long type) let $handler: (Config, [UserData], {str: int}) -> Result<[ProcessedData], Error> = build_pipeline(config:); ``` ### Try Blocks `try { }` is always stacked. Never inline. ```ori let $result = try { let $file = open(path:)?; let $data = read(file:)?; let $parsed = parse(input: data)?; validate(data: parsed)? }; ``` ### Unsafe Blocks Width-based. Inline if fits, stacked otherwise. ```ori // Inline let $value = unsafe { ptr_read(ptr:) }; // Stacked let $data = unsafe { let $ptr = get_raw_pointer(); let $value = ptr_read(ptr:); validate(value:) }; ``` ### If-Then-Else Inline if fits. `if cond then expr` stays together. Chained `else if` each on own line. `else` on new indented line when breaking. Mixed inline and block bodies are permitted. ```ori // Inline let $sign = if x > 0 then "positive" else "negative"; // Chained else-if let $grade = if score >= 90 then "A" else if score >= 80 then "B" else if score >= 70 then "C" else "F"; // Block bodies let $result = if condition1 then { let $x = compute_a(); process(x:) } else if condition2 then { let $y = compute_b(); process(y:) } else { default_value() }; // Long condition — breaks before && / || let $status = if user.is_active && user.has_permission(perm: "write") && !user.is_suspended then "active" else "inactive"; // Void if (no else) if should_log then log(msg: "event occurred"); if should_process then { validate(input:); process(input:); }; ``` ### For Loops with `yield` Inline if fits. Filter `if` stays on the same line. Complex yield body uses block `{ }`. Nested `for` each on own line. ```ori // Inline let $doubled = for x in items yield x * 2; let $evens = for x in items if x % 2 == 0 yield x; // Breaking — yield on own line let $names = for user in users yield user.profile.display_name; // With filter — breaking let $active = for user in users if user.is_active && user.age >= 18 yield user.name; // Block yield body let $records = for item in items yield { let $processed = validate(item:); let $formatted = format(data: processed); Record { data: formatted, timestamp: now() } }; // Nested for let $pairs = for x in xs for y in ys yield (x, y); // Yield to map — tuples become {K: V} let $lookup = for user in users yield (user.id, user.name); let $index = for item in items if item.is_active yield (item.key, item.value); ``` ### For Loops with `do` Inline if fits. Block `do { }` for multi-statement bodies. ```ori // Inline for item in items do print(msg: item); for x in items if x > 0 do process(value: x); // Block do body for user in users do { let $profile = fetch_profile(id: user.id); update_cache(key: user.id, value: profile); }; // With label for:outer item in items do { for:inner sub in item.children do { if sub.is_invalid then break:outer; process(sub:); }; }; ``` ### Loop Width-based. Inline if fits, stacked otherwise. ```ori // Inline loop { process_next() } // Stacked loop { let $input = read_input(); if input == "quit" then break; process(input:); } ``` Labels attach to the keyword with no space: `loop:name { }`. ### Match Always stacked. One arm per line. Trailing comma after every arm. ```ori let $msg = match status { Ok(value) -> `Success: {value}`, Err(e) -> `Error: {e}`, }; ``` Guards stay inline with the pattern. Block arm bodies use `-> { }` with closing `},` aligned. When a single-expression arm body exceeds width, break after `->` and indent the body. ```ori let $label = match score { n if n >= 90 -> "A", n if n >= 80 -> "B", _ -> "F", }; // Block arm body let $result = match event { Click(x, y, button) -> { let $target = find_target(x:, y:); handle_click(target:, button:) }, Close -> shutdown(), }; // Long single-expression arm — break after -> let $msg = match shape { Circle(r) -> `circle with radius {r} and area {3.14 * r ** 2}`, Point -> "point", }; ``` ### Or-Patterns in Match Inline if fits. When breaking, each alternative gets its own line with leading `|`. Body `->` goes on the last pattern's line. ```ori // Inline let $is_vowel = match c { 'a' | 'e' | 'i' | 'o' | 'u' -> true, _ -> false, }; // Broken — leading | let $msg = match error { NotFound(path:) | PermissionDenied(path:) | AccessError(path:) -> { log(msg: `File error: {path}`); default_value() }, Timeout -> retry(), }; ``` Expressions in arm bodies follow their own formatting rules. If-then-else and pipe chains in arms are inline if they fit; when the arm exceeds width, break after `->` and the body expression formats at the indented level. ```ori // If-then-else as arm body — inline let $sign = match n { x if x > 0 -> if x > 100 then "big" else "small", x if x < 0 -> "negative", _ -> "zero", }; // If-then-else as arm body — breaking let $label = match category { Premium(level) -> if level > 5 then "platinum" else if level > 3 then "gold" else "silver", Free -> "basic", }; // Pipe chain as arm body let $result = match input { Raw(data) -> data |> parse(format: Format.Json) |> validate(schema:) |> transform(config:), Cached(value) -> value, }; ``` ### Method-Style Match The `value.match(arms)` form formats as a regular method call. Arms are arguments. Inline if fits. One arm per line when broken, with trailing comma. Width-based breaking rules apply (not always-stacked like block `match`). ```ori // Inline let $label = status.match(Ok(v) -> v, Err(_) -> "unknown"); // Broken — one arm per line let $label = status.match( Ok(value) -> `success: {value}`, Err(e) -> `error: {e.to_str()}`, ); // With block arm body let $result = data.match( Valid(content) -> { let $processed = transform(content:); Ok(processed) }, Invalid(err) -> Err(err), ); ``` ### Method Chains Inline if fits. When any break is needed, receiver stays on the first line and all subsequent dots break (all-or-nothing). Method arguments break independently inside. ```ori // Inline let $result = items.filter(predicate: x -> x > 0).map(transform: x -> x * 2); // Broken — all dots break let $result = items .filter(predicate: x -> x > 0) .map(transform: x -> x * 2) .fold(initial: 0, op: (a, b) -> a + b); ``` Associated function calls (`Type.method()`) follow the same rules. Qualified dispatch (`Trait.method(value)`, `module.Type.method(value)`) has no space around `.`. Qualified associated type paths use `::` with no space (`Type::Trait::Assoc`). ```ori let $p = Point.new(x: 10, y: 20); let $result = Point.new(x: 1, y: 2) .distance_to(other: origin); // Qualified dispatch let $s = Printable.to_str(value); let $ord = Comparable.compare(a, other: b); type Item = Iterator::Item; ``` Parenthesized expressions as chain receivers stay on the first line. The contents break independently inside the parentheses. Index brackets `[]` are attached with no space and do not break independently — they are part of the preceding element in a chain. ```ori // Inline let $n = (a + b).abs(); let $total = (for x in items yield x).count(); // Chain breaks — paren group is receiver let $result = (for x in items yield x * 2) .filter(predicate: is_positive) .fold(initial: 0, op: (a, b) -> a + b); // Paren group itself is long — inner breaks let $result = ( for user in users if user.is_active yield user.score ) .filter(predicate: x -> x > threshold) .sum(); // Index chains — attached, no breaking between indexes let $cell = matrix[row][col]; let $val = data["key"][0]; // Mixed index + method chain — breaks at dots let $result = data["users"][0] .profile() .scores[0] .to_str(); ``` ### Pipe Chains Inline if fits. When breaking, the receiver stays on the first line and each `|>` goes on its own line at the same indentation level as the receiver. `|> .method()` calls a method on the piped value. ```ori // Inline let $result = x |> double() |> add(n: 1); // Broken — each |> on own line let $processed = data |> transform(factor: 2) |> filter(predicate: is_positive) |> collect(); // Pipe to method let $cleaned = text |> .trim() |> .upper(); // Pipe with lambda fallback let $formatted = value |> (n -> `result: {n}`); ``` Each `|>` step should be a single operation. When pipe steps and method chains are mixed, pipe breaks take precedence over chain breaks. The idiomatic style is one operation per `|>` step: ```ori // Idiomatic: one operation per |> step let $r = data |> transform() |> .filter(x -> x > 0) |> .map(y -> y * 2) |> .collect(); // If user writes mixed, pipe breaks take precedence let $r = data |> transform() |> .filter(x -> x > 0).map(y -> y * 2); ``` ### Binary Expressions Inline if fits. Break before the operator. First operand on assignment line. Continuation lines start with the operator, indented. ```ori let $result = first_value + second_value - third_value * fourth_value + fifth_value; // || chains: each clause on own line let $valid = is_admin || is_moderator || has_permission(perm: "write"); ``` ### Type Conversions `as` and `as?` have space around (like binary operators) and break before `as` when long. `as?` is a single token (no space before `?`). ```ori // Inline let $f = n as float; let $parsed = s as? int; // Long — break before as let $converted = compute_intermediate_result(input: value) as OtherType; ``` ### Lambdas Inline if fits. No parens for single untyped param. Block body for multi-statement. Typed lambdas `(x: int) -> int = expr` follow the same rules with annotations inline; break at `=` when long. ```ori // Inline x -> x + 1 (a, b) -> a + b () -> 42 // Typed lambda let $transform = (x: int) -> int = x * 2; // Long typed lambda — break at = let $handler = (req: Request, ctx: Context) -> Result = process_request(req:, ctx:); // Block body (x) -> { let $processed = validate(value: x); transform(processed:) } // Curried lambdas — inline if fits let $add = x -> y -> x + y; let $add3 = x -> y -> z -> x + y + z; // Curried — breaking, inner is body of outer let $builder = config -> request -> response -> build_handler(config:, request:, response:); ``` ### Labels No space around `:` in labels. Label attaches directly to the keyword. ```ori loop:outer { loop:inner { if done then break:outer; if skip then continue:inner; }; }; ``` ### Break and Continue with Value Space between `break`/`continue` and value. Value on the same line when short. Long value breaks to the next indented line. Labeled forms use `break:label value` and `continue:label value`. ```ori let $found = loop { let $item = next(); if item.matches(query:) then break item; }; // continue with value (substitution in yield) let $results = for x in items yield { if x < 0 then continue 0; compute(x:) }; ``` ### Error Propagation `?` is attached to the expression with no space (postfix). `??` is a binary operator with space around that breaks before the operator like other binary operators. `?` and `??` compose naturally with no special interaction — `?` attaches to its operand, then `??` follows standard binary breaking rules. ```ori // ? attached let $data = read_file(path:)?; // ? in method chains — stays attached, chain breaks after ? let $name = get_user(id:)?.profile()?.display_name(); let $name = get_user(id:)? .profile()? .display_name(); // ?? breaks before operator let $connection = try_primary_db() ?? try_secondary_db() ?? try_fallback_db() ?? panic(msg: "no database available"); // ? then ?? — compose naturally let $conn = try_connect()? ?? default_connection(); let $data = fetch_primary()? ?? fetch_secondary()? ?? fallback_data(); ``` ### Assignments Compound assignment operators (`+=`, `-=`, `|=`, etc.) have space around the operator. Index and field assignments format like `let` bindings. ```ori count += 1; list[0] = new_value; state.name = new_name; state.items[i] = new_item; ``` --- ## Literals and Collections ### Lists Simple items (literals, identifiers) wrap (bin-pack) when broken. Complex items (structs, calls, nested collections) one per line. ```ori // Inline let $nums = [1, 2, 3, 4, 5]; // Simple items — wrap let $nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ]; // Complex items — one per line let $users = [ User { id: 1, name: "Alice" }, User { id: 2, name: "Bob" }, ]; ``` ### Maps Inline if fits. One entry per line when broken. Computed keys `[expr]` have no space inside the brackets and are treated as regular entries. ```ori let $m = { "a": 1, "b": 2 }; let $config = { "name": "Alice", "age": 30, "email": "alice@example.com", }; // Computed keys let $dynamic = {[key]: value}; let $mixed = { "version": "1.0", [env]: "active", "debug": "false", }; ``` ### Tuples and Struct Literals Width-based. One element/field per line when broken. Field shorthand supported. ```ori let $pair = (1, "hello"); let $p = Point { x: 0, y: 0 }; // Broken let $config = Config { timeout: 30s, max_retries: 3, base_url: "https://api.example.com", }; ``` The trailing comma in single-element tuples is semantic — `(int,)` is a tuple type, `(int)` is a parenthesized type. The formatter shall preserve this distinction exactly as written, never adding or removing the comma. ```ori // Single-element tuple — comma preserved let $wrapped: (int,) = (42,); type Singleton = (str,); // Parenthesized type — no comma, just grouping let $x: (int) = 42; ``` ### Spread `...` attached to the expression with no space. Spread is treated as a regular entry — inline if fits, on its own line when broken. In struct updates, the spread goes first, followed by field overrides. ```ori // Inline let $combined = [...first, ...second]; let $updated = { ...config, debug: true }; // Broken — spread first, overrides follow let $updated = { ...config, host: "production.example.com", port: 443, timeout: 60s, debug: false, }; // Multiple spreads let $merged = { ...defaults, ...overrides, version: "2.0", }; ``` ### Ranges No space around `..`/`..=`. Space around `by`. ```ori 0..10 0..=100 0..100 by 5 10..0 by -1 ``` ### Literals All literals are atomic tokens — never broken, never reformatted internally. Numeric format (hex casing, underscore placement, base, exponent notation) is preserved as-written. Duration and size suffixes are part of the token with no space before the suffix. ```ori // Character literals let $ch = 'a'; let $nl = '\n'; // Duration/size — suffix attached let $timeout = 30s; let $delay = 100ms; let $limit = 4kb; // Numeric formats preserved let $million = 1_000_000; let $mask = 0xFF; let $addr = 0xDEAD_BEEF; let $flags = 0b1010_0101; let $tiny = 2.5e-8; ``` `embed(path)` and `has_embed(path)` are regular function-like expressions. The path string is atomic. No special formatting. ### Strings and Templates Never break inside string or template string content. Break the containing construct instead. No space inside `{}` interpolation braces. ```ori // Break the binding, not the string let $report = `User {user.name} logged in from {ip} at {timestamp}`; // Extract to variables for very long templates let $user_info = user.display_name; let $report = `User {user_info} logged in from {location} at {time}`; ``` This rule applies recursively to nested template strings (a template inside another template's interpolation). When too long, break the binding, not the template. ```ori // Nested template — atomic at every level let $msg = `User {`{first} {last}`} logged in`; // Too long — break the binding let $notification = `Alert: {`User {user.name} (ID: {user.id})`} performed {action}`; ``` ### Named Arguments Punning form `name:` (no space after colon) when variable matches parameter. Full form `name: value` (space after colon) otherwise. ```ori // Punning let $p = Point.new(x:, y:); // Full form let $p = Point.new(x: 10, y: 20); // Mixed let $conn = Database.connect( host:, port:, username: "admin", password: get_password(), ); // Single-param with inline lambda — no name needed list.map(x -> x * 2); ``` ### Expression Arguments Block expressions and `for`-yield expressions used as arguments follow standard expression rules. The block or `for`-yield breaks independently inside the argument position. ```ori // Block as argument — inline if fits let $v = f(data: { let $x = 1; x + 2 }); // Block as argument — broken let $result = process( data: { let $x = compute(); let $y = transform(x:); validate(y:) }, timeout: 30s, ); // For-yield as argument — inline if fits let $total = sum(items: for x in xs yield x * 2); // For-yield as argument — broken let $result = process( items: for user in users if user.is_active yield user.score, timeout: 30s, ); ``` --- ## Control Flow Expressions ### `with...in` Capability Binding Inline if fits. When breaking, capabilities are comma-separated and aligned under the first. `in` keyword on its own line. Stateful handlers are always stacked. ```ori // Inline let $result = with Http = mock in fetch(url:); // Multiple — breaking let $result = with Http = mock, Cache = mock, Clock = test_clock in { let $data = fetch(url:); process(data:) }; // Stateful handler — always stacked let $result = with Logger = handler(state: []) { log: (s, msg) -> { ([...s, msg], void) }, } in { log(msg: "starting"); do_work() }; // Nested with...in — each level indented let $result = with Http = MockHttp in with Cache = MockCache in { fetch_cached(url:) }; // Prefer: multiple bindings on single with let $result = with Http = MockHttp, Cache = MockCache in { fetch_cached(url:) }; ``` ### Always-Stacked Function Expressions `recurse`, `parallel`, `spawn`, `nursery` are always stacked. Named arguments one per line. Lambda bodies break independently. Trailing comma. ```ori let $factorial = recurse( condition: n -> n <= 1, base: _ -> 1, step: (n, self) -> n * self(n - 1), ); let $results = parallel( tasks: [ () -> fetch(url: url1), () -> fetch(url: url2), ], max_concurrent: 2, timeout: 30s, ); nursery( body: (n) -> { n.spawn(task: () -> worker(id: 1)); n.spawn(task: () -> worker(id: 2)); }, on_error: NurseryErrorMode.CancelRemaining, timeout: 60s, ); ``` ### Width-Based Function Expressions `timeout`, `cache`, `catch`, `with()` follow width-based breaking. Inline when short, stacked when long. ```ori // Inline let $result = catch(expr: parse(input:)); let $data = timeout(op: () -> fetch(url:), after: 5s); // Stacked let $result = with( acquire: () -> open_file(path:), action: (file) -> { let $data = read(file:); process(data:) }, release: (file) -> close(file:), ); ``` --- ## Module Organization ### File Layout The formatter enforces this ordering at the top of the file: 1. File-level attributes (`#!target(...)`) 2. Imports (sorted and grouped) 3. Constants (grouped, user blank lines preserved) 4. Everything else in user-defined order ```ori #!target(os: "linux") use std.collections { HashMap }; use std.io { read_file, write_file }; use "../config" { Config }; use "./models" { User }; extension std.iter.extensions { Iterator.count }; let $VERSION = "1.0.0"; let $MAX_RETRIES = 3; type AppConfig = { ... }; @process () -> void = { ... } @main () -> void = { ... } ``` ### Imports Stdlib imports first, relative imports second. Each group sorted alphabetically by module path. Blank line between groups. Items within `{ }` sorted alphabetically. ```ori // Group 1: stdlib (sorted) use std.collections { BTreeMap, HashMap, HashSet }; use std.io { read_file, write_file }; use std.testing { assert_eq }; // Group 2: relative (sorted) use "../config" { Config, defaults }; use "./models" { Post, User }; use "./utils" { format_date, validate }; ``` `without def` is part of the import item with spaces around `without`: ```ori use "./traits" { Printable without def } use "./traits" { Clone without def, Debug, Printable without def, }; ``` Extension imports appear after regular imports in the same group. Methods sorted alphabetically. ```ori extension std.iter.extensions { Iterator.count }; extension std.collections.extensions { List.chunk, List.flatten, List.unique, Map.merge, }; ``` ### Extern Blocks Opening `{` on the same line. Declarations indented. `as "alias"` stays on the declaration line. Block-level annotations (`#error`, `#free`) appear on the extern header line. Per-function annotations appear before `as`. `out`, `owned`, `borrowed` are inline with parameters. ```ori extern "c" from "libm" { @_sin (x: float) -> float as "sin"; @_cos (x: float) -> float as "cos"; @_sqrt (x: float) -> float as "sqrt"; } extern "js" from "./utils.js" { @_parse (input: str) -> JsValue as "parse"; } // Error protocol on block header extern "c" from "sqlite3" #error(errno) { @_open (path: str, db: out CPtr) -> c_int as "sqlite3_open"; @_close (db: CPtr) -> c_int as "sqlite3_close"; } // Ownership + free on block extern "c" from "lib" #free(free) { @_alloc (size: c_size) -> owned CPtr as "malloc"; @_get_name (obj: borrowed CPtr) -> borrowed str as "get_name"; } // Per-function #error opt-out extern "c" from "lib" #error(errno) { @_read (fd: c_int, buf: [byte]) -> c_int as "read"; @_close (fd: c_int) -> c_int #error(none) as "close"; } // C variadics extern "c" { @_printf (fmt: CPtr, ...) -> c_int as "printf"; } ``` Parametric FFI capabilities use `uses FFI("library")` and follow normal capability formatting: ```ori @query (db: CPtr, sql: str) -> Result<[Row], str> uses FFI("sqlite3") = { ... } ``` ### Capset Declarations Inline if fits. Capabilities sorted alphabetically. One per line when broken with trailing comma. ```ori capset Net = Dns, Http, Tls; capset Full = Cache, Clock, Crypto, Dns, FileSystem, Http, Logger, Print, Random, Tls; ``` --- ## Attributes Each attribute on its own line above the declaration. Multiple attributes use canonical order: `#target`/`#cfg` → `#repr` → `#derive` → `#skip`/`#compile_fail`/`#fail`. No blank lines between stacked attributes. ```ori #target(os: "linux") #repr("c") #derive(Eq, Clone, Debug) type NativeBuffer = { ptr: CPtr, len: c_size, cap: c_size, }; ``` Conditional compilation attributes follow width-based breaking: inline if fits, one condition per line otherwise. ```ori #target(os: "linux", arch: "x86_64") @simd_add (a: int, b: int) -> int = { ... } #target( os: "linux", arch: "x86_64", family: "unix", ) @platform_specific () -> void = { ... } ``` `#repr` attributes are always inline. ```ori #repr("c") #repr("aligned", 16) #repr("transparent") ``` `#derive` follows width-based breaking: inline if fits, one trait per line when broken. Traits are sorted alphabetically within the derive. ```ori // Inline #derive(Eq, Clone, Debug) type Point = { x: int, y: int }; // Broken — one per line, sorted #derive( Clone, Comparable, Debug, Default, Eq, Formattable, Hashable, Printable, ) type Config = { timeout: Duration, retries: int, }; ``` Comments between stacked attributes are not permitted. Attributes form a cohesive unit attached to their declaration. The formatter moves intervening comments above the attribute block. ```ori // Input #target(os: "linux") // platform-specific #derive(Eq, Clone) type NativeBuffer = { ... }; // Formatted — comment moved above // platform-specific #target(os: "linux") #derive(Eq, Clone) type NativeBuffer = { ... }; ``` --- ## Destructuring Width-based. One element per line when broken. Rest patterns `..rest` and `..$rest` have no space before `..`. Nested patterns break independently. ```ori // Inline let (x, y) = get_point(); let { name, age } = get_user(); let [$head, ..tail] = items; // Broken — one per line let { name, email, age, address, phone, } = get_full_profile(); // Nested — breaks independently let { name, address: { street, city, state, zip, }, } = get_user(); ``` --- ## Parentheses All user parentheses are preserved. The formatter never removes parentheses, even when not strictly required for precedence. This ensures the formatter cannot change program semantics. ```ori // Preserved for clarity let $x = (a + b) * c; // Preserved: user intent let $y = (1 + 2); // Required: expression as method receiver (for x in items yield x * 2).fold(initial: 0, op: (a, b) -> a + b) // Required: lambda as call target (x -> x * 2)(5) ``` --- ## Comments Comments shall appear on their own line. Inline (end-of-line) comments are prohibited. The formatter normalizes spacing: `//comment` becomes `// comment`. ### Doc Comments Space after `//`, space after doc marker. Formatter enforces marker order and reorders `*` entries to match declaration parameter order. | Order | Marker | Purpose | |-------|--------|---------| | 1 | *(none)* | Description | | 2 | `*` | Parameters/fields | | 3 | `!` | Errors/warnings | | 4 | `>` | Examples | ```ori // Computes the sum of two integers. // * a: The first operand. // * b: The second operand. // ! Panics if overflow occurs. // > add(a: 2, b: 3) -> 5 @add (a: int, b: int) -> int = a + b; ``` Normalizations: | Input | Output | |-------|--------| | `//comment` | `// comment` | | `// comment` | `// comment` | | `//*name:` | `// * name:` | | `//!Warning` | `// ! Warning` | | `//>example` | `// > example` | --- --- title: "Annex E — System considerations" description: "Ori Language Specification — Annex E (informative)" order: 104 section: "Annexes" --- # Annex E (informative) — System considerations This annex describes implementation considerations for different target platforms and optimization levels. This section specifies implementation-level requirements and platform considerations. ## Numeric Types ### Integers The `int` type is a signed integer with the following semantic range: | Property | Value | |----------|-------| | Canonical size | 64 bits | | Minimum | -9,223,372,036,854,775,808 (-2⁶³) | | Maximum | 9,223,372,036,854,775,807 (2⁶³ - 1) | | Overflow | Panics (see [Error Codes](https://ori-lang.com/docs/compiler-design/appendices/c-error-codes)) | The canonical size defines the semantic range. The compiler may use a narrower machine representation (see [§ Representation Optimization](#representation-optimization)). There is no separate unsigned integer type. Bitwise operations treat the value as unsigned bits. ### Floats The `float` type is an IEEE 754 double-precision floating-point number: | Property | Value | |----------|-------| | Canonical size | 64 bits | | Precision | ~15-17 significant decimal digits | | Range | ±1.7976931348623157 × 10³⁰⁸ | The canonical size defines the semantic precision. The compiler may use a narrower machine representation when it can prove no precision loss (see [§ Representation Optimization](#representation-optimization)). Special values `inf`, `-inf`, and `nan` are supported. ## Strings ### Encoding All strings are UTF-8 encoded. There is no separate ASCII or byte-string type. ```ori let greeting = "Hello, 世界"; // UTF-8 let emoji = "🎉"; // UTF-8 ``` ### Indexing String indexing returns a single Unicode codepoint as a `str`: ```ori let s = "héllo"; s[0]; // "h" s[1] // "é" (single codepoint) ``` The index refers to codepoint position, not byte position. Out-of-bounds indexing panics. ### Grapheme Clusters Some visual characters consist of multiple codepoints: ```ori let astronaut = "🧑‍🚀"; // 3 codepoints: person + ZWJ + rocket len(astronaut); // 3 astronaut[0] // "🧑" ``` For grapheme-aware operations, use standard library functions. ### Length `len(str)` returns the number of bytes, not codepoints. Use `.chars().count()` for codepoint count. ```ori len("hello") // 5 (5 bytes) len("世界") // 6 (each character is 3 UTF-8 bytes) len("🧑‍🚀") // 11 (multi-byte emoji ZWJ sequence: 4+3+4) ``` ## Collections ### Limits Collections have no fixed size limits. Maximum size is bounded by available memory. | Collection | Limit | |------------|-------| | List | Memory | | Map | Memory | | String | Memory | ### Capacity Implementations may pre-allocate capacity for performance. This is not observable behavior. ## Recursion ### Tail Call Optimization Tail calls are guaranteed to be optimized. A tail call does not consume stack space: ```ori @countdown (n: int) -> void = if n <= 0 then void else countdown(n: n - 1); // tail call countdown(n: 1000000) // does not overflow stack ``` A call is in tail position if it is the last operation before the function returns. ### Non-Tail Recursion Non-tail recursive calls consume stack space. Deep recursion may cause stack overflow: ```ori @sum_to (n: int) -> int = if n <= 0 then 0 else n + sum_to(n: n - 1); // not tail call sum_to(n: 1000000) // may overflow stack ``` For deep recursion, use the `recurse` pattern with `memo: true` or convert to tail recursion. ## Platform Support ### Target Platforms Conforming implementations should support: - Linux (x86-64, ARM64) - macOS (x86-64, ARM64) - Windows (x86-64) - WebAssembly (WASM) ### Endianness Byte order is implementation-defined. Programs should not depend on endianness unless using platform-specific byte manipulation. ### Path Separators File paths use the platform-native separator. The standard library provides cross-platform path operations. ## Implementation Limits Implementations may impose limits on: | Aspect | Minimum Required | |--------|------------------| | Identifier length | 1024 characters | | Nesting depth | 256 levels | | Function parameters | 255 | | Generic parameters | 64 | Exceeding these limits is a compile-time error. ## Representation Optimization The compiler may optimize the machine representation of any type, provided the optimization preserves _semantic equivalence_. An optimization is semantically equivalent if no conforming program can distinguish the optimized representation from the canonical one through any language-level operation. ### Canonical Representations | Type | Canonical | Semantic Range | |------|-----------|----------------| | `int` | 64-bit signed two's complement | [-2⁶³, 2⁶³ - 1] | | `float` | 64-bit IEEE 754 binary64 | ±1.8 × 10³⁰⁸, ~15-17 digits | | `bool` | 1-bit | `true` or `false` | | `byte` | 8-bit unsigned | [0, 255] | | `char` | 32-bit Unicode scalar | U+0000–U+10FFFF excluding surrogates | | `Ordering` | Tri-state | `Less`, `Equal`, `Greater` | ### Permitted Optimizations Permitted optimizations include but are not limited to: - Narrowing primitive machine types (`bool` → `i1`, `byte` → `i8`, `char` → `i32`, `Ordering` → `i8`) - Enum discriminant narrowing (`i8` for ≤256 variants) - All-unit enum payload elimination - Sum type shared payload slots (`Result` uses `max(sizeof(T), sizeof(E))`) - ARC operation elision for transitively trivial types - Newtype representation erasure - Struct field reordering for alignment - Integer narrowing based on value range analysis - Float narrowing when precision loss is provably zero ### Guarantees 1. The semantic range of every type is always preserved 2. Overflow behavior is determined by the semantic type, not the machine representation 3. Values stored and retrieved through any language operation are identical 4. `debug()` and `print()` display semantic values 5. `x == y` and `hash(x) == hash(y)` relationships are representation-independent 6. Type classification for reference counting is determined by type containment, not representation size (see [Memory Model § Type Classification](21-memory-model.md#217-type-classification)) ### Non-Guarantees 1. The exact machine representation of any type is unspecified 2. Memory layout may differ between compiler versions and target platforms 3. Struct field order in memory may differ from declaration order NOTE For the full specification including optimization tiers, cross-cutting invariants, and interaction with `#repr` attributes, see [Representation Optimization Proposal](../../proposals/approved/representation-optimization-proposal.md). ## ARC Runtime This section specifies the runtime support for reference-counted heap objects in AOT-compiled programs. NOTE The ARC runtime ABI is not stable. Heap object layout and runtime function signatures may change between compiler versions. This section applies to the AOT compilation target only; the interpreter and JIT may use different representations. ### Heap Object Layout A reference-counted heap object has the following layout: ``` +──────────────────+───────────────────────────+ | strong_count: i64 | data bytes ... | +──────────────────+───────────────────────────+ ^ ^ base (data_ptr - 8) data_ptr ``` The `data_ptr` returned by allocation points to the data area, not to the header. The strong count is stored at `data_ptr - 8`. Minimum alignment is 8 bytes. The data pointer may be passed to foreign functions without adjustment. ### Runtime Functions All runtime functions use the C calling convention (`extern "C"`). | Function | Signature | Description | |----------|-----------|-------------| | `ori_rc_alloc` | `(size: usize, align: usize) -> *mut u8` | Allocate `size + 8` bytes, initialize strong count to 1, return data pointer | | `ori_rc_inc` | `(data_ptr: *mut u8)` | Increment the strong count | | `ori_rc_dec` | `(data_ptr: *mut u8, drop_fn: fn(*mut u8))` | Decrement the strong count; if zero, call `drop_fn` | | `ori_rc_free` | `(data_ptr: *mut u8, size: usize, align: usize)` | Deallocate from `data_ptr - 8` with total size `size + 8` | | `ori_rc_count` | `(data_ptr: *const u8) -> i64` | Return the current strong count (diagnostic use only) | ### Drop Functions Each reference type has a compiler-generated _drop function_ with signature `extern "C" fn(*mut u8)`. The drop function: 1. Decrements reference counts of any reference-typed child fields (calling `ori_rc_dec` for each) 2. Calls `ori_rc_free(data_ptr, size, align)` to release the allocation If the type implements the `Drop` trait, `Drop.drop` is called before step 1. ### Built-in Type Representations | Type | Representation | |------|----------------| | `str` | `{ len: i64, data: *const u8 }` | | `[T]` | `{ len: i64, cap: i64, data: *mut u8 }` | | `Option` | `{ tag: i8, value: T }` (tag 0 = `None`, 1 = `Some`) | | `Result` | `{ tag: i8, value: max(T, E) }` (tag 0 = `Ok`, 1 = `Err`) | --- # Example programs (passing conformance tests) ## tests/spec/traits/generic_impl.ori — generic trait implementations (`impl Type: Trait`) ```ori // Spec: 08-declarations.md § Implementations // Tests for generic impl blocks use std.testing { assert, assert_eq } // ============================================================================= // Setup: Generic Types and Traits // ============================================================================= type Box = { value: T } type Pair = { first: A, second: B } trait Describable { @describe (self) -> str } trait Container { @get_size (self) -> int } // ============================================================================= // Inherent Impl on Generic Type // ============================================================================= impl Box { @unwrap (self) -> T = self.value; } impl Pair { @swap (self) -> Pair = Pair { first: self.second, second: self.first } } // ============================================================================= // Trait Impl for Generic Type // ============================================================================= impl Container for Box { @get_size (self) -> int = 1; } // ============================================================================= // Tests for Inherent Methods // ============================================================================= @test_box_unwrap tests _ () -> void = { let b = Box { value: 42 }; assert_eq(actual: b.unwrap(), expected: 42) } @test_box_unwrap_str tests _ () -> void = { let b = Box { value: "hello" }; assert_eq(actual: b.unwrap(), expected: "hello") } @test_pair_swap tests _ () -> void = { let p = Pair { first: 1, second: "one" }; let swapped = p.swap(); assert_eq(actual: swapped.first, expected: "one"); assert_eq(actual: swapped.second, expected: 1) } // ============================================================================= // Tests for Trait Methods // ============================================================================= @test_box_container tests _ () -> void = { let b = Box { value: [1, 2, 3] }; assert_eq(actual: b.get_size(), expected: 1) } ``` ## tests/spec/traits/default_assoc_types.ori — associated types with defaults ```ori // Spec: 08-declarations.md § Traits (Default Associated Types) // Tests for default associated types on traits use std.testing { assert, assert_eq } // ============================================================================= // Basic Default Associated Types // ============================================================================= // Trait with default associated type (Output defaults to Self) trait Addable { type Output = Self; @add (self, rhs: Rhs) -> Self.Output } // ============================================================================= // Type Definitions for Tests // ============================================================================= type Point = { x: int, y: int } // Helper function to create points @make_point (x: int, y: int) -> Point = Point { x, y } // Impl without Output - uses default (Output = Self = Point) impl Addable for Point { @add (self, rhs: Point) -> Point = Point { x: self.x + rhs.x, y: self.y + rhs.y } } // ============================================================================= // Override Default Associated Type // ============================================================================= type Number = { value: int } @make_number (value: int) -> Number = Number { value } // Helper function to add numbers and return raw int @add_numbers (a: Number, b: Number) -> int = a.add(rhs: b); // Impl that overrides the default Output type impl Addable for Number { type Output = int; @add (self, rhs: Number) -> int = self.value + rhs.value; } // ============================================================================= // Tests // ============================================================================= @test tests @make_point () -> void = { let p = make_point(x: 1, y: 2); assert_eq(actual: p.x, expected: 1); assert_eq(actual: p.y, expected: 2) } // Test default associated type (Output = Self = Point) @test tests @add () -> void = { let p1 = make_point(x: 1, y: 2); let p2 = make_point(x: 3, y: 4); let result = p1.add(rhs: p2); assert_eq(actual: result.x, expected: 4); assert_eq(actual: result.y, expected: 6) } @test tests @make_number () -> void = { let n = make_number(value: 42); assert_eq(actual: n.value, expected: 42) } // Test explicit associated type override (Output = int, not Self) @test tests @add_numbers () -> void = { let n1 = make_number(value: 10); let n2 = make_number(value: 20); let result = add_numbers(a: n1, b: n2); assert_eq(actual: result, expected: 30) } ``` ## tests/spec/traits/object_safety.ori — trait objects and object safety ```ori // Object safety: verifies that object-safe traits are accepted as types // Spec: 06-types.md § Object Safety Rules // // Note: Full trait object dispatch (passing concrete types as trait objects) // requires coercion support. See compile-fail/ tests for object safety // error detection (the primary deliverable of Section 3.11). use std.testing { assert_eq } // Object-safe: returns str, not Self — no E2024 trait Describable { @describe (self) -> str } // Object-safe: returns int, no Self in params — no E2024 trait Hashable { @hash (self) -> int } type Point = { x: int, y: int } impl Describable for Point { @describe (self) -> str = "Point(" + self.x.to_str() + ", " + self.y.to_str() + ")"; } impl Hashable for Point { @hash (self) -> int = self.x * 31 + self.y; } // Verify: calling trait methods directly on concrete types still works @test_trait_method_call tests _ () -> void = { let p = Point { x: 3, y: 4 }; assert_eq(actual: p.describe(), expected: "Point(3, 4)"); assert_eq(actual: p.hash(), expected: 97) } ``` ## tests/spec/capabilities/declaration.ori — declaring capability effects with `uses` ```ori // Spec: 14-capabilities.md § Declaration use std.testing { assert_eq } // Tests for capability declaration syntax (uses clause) // ============================================================================= // Basic Capability Declaration // ============================================================================= // Function with single capability @fetch_data (url: str) -> str uses Http = url; // Function with multiple capabilities @save_and_log (data: str) -> void uses FileSystem, Logger = (); // Function with capability and return type @read_file (path: str) -> Result uses FileSystem = Ok(path); // Pure function - no uses clause @add (a: int, b: int) -> int = a + b; // ============================================================================= // Capability with Generics // ============================================================================= @process (data: T) -> T uses Logger where T: Clone = data; // ============================================================================= // Tests // ============================================================================= // Test pure function works @test_pure_function tests @add () -> void = { assert_eq( actual: add(a: 1, b: 2), expected: 3, ) } // Test function with capability can be called (when capability is provided) @test_with_capability tests @fetch_data () -> void = { // Provide a mock capability let result = with Http = "mock" in fetch_data(url: "test"); assert_eq( actual: result, expected: "test", ) } // Test generic function with capability @test_generic_with_capability tests @process () -> void = { let result = with Logger = "mock" in process(data: 42); assert_eq( actual: result, expected: 42, ) } ``` ## tests/spec/capabilities/providing.ori — providing capabilities with `with Cap = impl in expr` (mocking) ```ori // Spec: 14-capabilities.md § Providing Capabilities use std.testing { assert_eq } // Tests for with...in expression (capability provision) // ============================================================================= // Basic Capability Provision // ============================================================================= // Simple with...in expression @test_basic_provision tests _ () -> void = { let result = with Http = "mock_http" in Http; assert_eq( actual: result, expected: "mock_http", ) } // With struct provider type MockHttp = { base_url: str } @test_struct_provider tests _ () -> void = { let result = with Http = MockHttp { base_url: "https://api.example.com" } in Http.base_url; assert_eq( actual: result, expected: "https://api.example.com", ) } // ============================================================================= // Capability Scoping // ============================================================================= // Capability is only available in body @test_scoping tests _ () -> void = { let outer = "outer"; let inner = with Http = "http" in Http; assert_eq(actual: outer, expected: "outer"); assert_eq(actual: inner, expected: "http") } // ============================================================================= // Nested Capability Provision // ============================================================================= // Multiple capabilities via nesting @test_nested_provision tests _ () -> void = { let result = with Http = "http" in with Cache = "cache" in Http + "-" + Cache; assert_eq( actual: result, expected: "http-cache", ) } // Inner shadows outer @test_shadowing tests _ () -> void = { let result = with Http = "outer" in with Http = "inner" in Http; assert_eq( actual: result, expected: "inner", ) } // ============================================================================= // With Expression Returns Body Value // ============================================================================= @test_returns_body_value tests _ () -> void = { let result = with Http = "unused" in 42; assert_eq( actual: result, expected: 42, ) } @test_complex_body tests _ () -> void = { let result = with Logger = "log" in { let x = 10; let y = 20; x + y }; assert_eq( actual: result, expected: 30, ) } // ============================================================================= // Extended Capability Tests // ============================================================================= // Test capability with different types @test_int_capability tests _ () -> void = { let result = with Counter = 42 in Counter + 1; assert_eq(actual: result, expected: 43) } @test_bool_capability tests _ () -> void = { let result = with Debug = true in if Debug then "on" else "off"; assert_eq(actual: result, expected: "on") } // Test capability with list value type MockDataStore = { data: [int] } @test_list_capability tests _ () -> void = { let store = MockDataStore { data: [1, 2, 3] }; let result = with DataStore = store in DataStore.data[0]; assert_eq(actual: result, expected: 1) } // Test capability used in conditional @test_capability_in_conditional tests _ () -> void = { let result = with Debug = true in if Debug then "debug mode" else "release mode"; assert_eq(actual: result, expected: "debug mode") } // Test capability with multiple uses in body @test_multiple_uses tests _ () -> void = { let result = with Value = 10 in Value + Value + Value; assert_eq(actual: result, expected: 30) } // Test deeply nested capabilities (three levels) @test_three_level_nesting tests _ () -> void = { let result = with A = 1 in with B = 2 in with C = 3 in A + B + C; assert_eq(actual: result, expected: 6) } // Test capability with closure that doesn't capture it @test_capability_not_in_closure tests _ () -> void = { let f = (x: int) -> x * 2; let result = with Logger = "log" in f(5); assert_eq(actual: result, expected: 10) } // Test capability visible in let binding @test_capability_in_let tests _ () -> void = { let result = with X = 100 in { let y = X / 10; y }; assert_eq(actual: result, expected: 10) } // Test capability with struct method access type Config = { name: str, port: int } impl Config { @get_address (self) -> str = self.name + ":" + str(self.port); } @test_capability_method_call tests _ () -> void = { let cfg = Config { name: "localhost", port: 8080 }; let result = with Cfg = cfg in Cfg.get_address(); assert_eq(actual: result, expected: "localhost:8080") } // Test capability preserves through function call @uses_cap () -> int uses Value = Value; @test_capability_through_function tests _ () -> void = { let result = with Value = 42 in uses_cap(); assert_eq(actual: result, expected: 42) } ``` ## tests/spec/capabilities/async.ori — capabilities + Suspend (async) ```ori // Spec: 14-capabilities.md § Async Capability // // NOTE: Ori does not have async/await syntax. // Concurrency is handled through the `parallel` pattern. // See tests/spec/patterns/concurrency.ori for parallel pattern tests. // // This file is intentionally empty - async is not a language feature. ``` ## tests/spec/patterns/exhaustiveness.ori — match expressions and exhaustiveness ```ori // Spec: 10-patterns.md § exhaustiveness // Conformance: exhaustive match expressions compile and execute correctly. // These verify the exhaustiveness checker accepts valid matches. use std.testing { assert_eq } // ============================================================================= // Option exhaustive // ============================================================================= @test_option_exhaustive tests @option_exhaustive () -> void = { assert_eq(actual: option_exhaustive(Some(42)), expected: 42); assert_eq(actual: option_exhaustive(None), expected: 0) } @option_exhaustive (opt: Option) -> int = match opt { Some(v) -> v, None -> 0 } // ============================================================================= // Result exhaustive // ============================================================================= @test_result_exhaustive tests @result_exhaustive () -> void = { assert_eq(actual: result_exhaustive(Ok(10)), expected: 10); assert_eq(actual: result_exhaustive(Err("fail")), expected: -1) } @result_exhaustive (res: Result) -> int = match res { Ok(v) -> v, Err(_) -> -1 } // ============================================================================= // User-defined enum exhaustive (unit variants) // ============================================================================= type Color = Red | Green | Blue; @test_color_exhaustive tests @color_exhaustive () -> void = { assert_eq(actual: color_exhaustive(Red), expected: "red"); assert_eq(actual: color_exhaustive(Green), expected: "green"); assert_eq(actual: color_exhaustive(Blue), expected: "blue") } @color_exhaustive (c: Color) -> str = match c { Red -> "red", Green -> "green", Blue -> "blue" } // ============================================================================= // User-defined enum exhaustive (variants with fields) // ============================================================================= type Shape = Circle(radius: int) | Rectangle(width: int, height: int); @test_shape_exhaustive tests @shape_exhaustive () -> void = { assert_eq(actual: shape_exhaustive(Circle(radius: 5)), expected: 5); assert_eq(actual: shape_exhaustive(Rectangle(width: 3, height: 4)), expected: 12) } @shape_exhaustive (s: Shape) -> int = match s { Circle(r) -> r, Rectangle(w, h) -> w * h } // ============================================================================= // Bool exhaustive (true, false) // ============================================================================= @test_bool_exhaustive tests @bool_exhaustive () -> void = { assert_eq(actual: bool_exhaustive(true), expected: 1); assert_eq(actual: bool_exhaustive(false), expected: 0) } @bool_exhaustive (b: bool) -> int = match b { true -> 1, false -> 0 } // ============================================================================= // Option with wildcard fallback // ============================================================================= @test_option_wildcard tests @option_wildcard () -> void = { assert_eq(actual: option_wildcard(Some(99)), expected: 99); assert_eq(actual: option_wildcard(None), expected: -1) } @option_wildcard (opt: Option) -> int = match opt { Some(v) -> v, _ -> -1 } // ============================================================================= // Int with wildcard (infinite type) // ============================================================================= @test_int_wildcard tests @int_wildcard () -> void = { assert_eq(actual: int_wildcard(0), expected: "zero"); assert_eq(actual: int_wildcard(1), expected: "one"); assert_eq(actual: int_wildcard(42), expected: "other") } @int_wildcard (n: int) -> str = match n { 0 -> "zero", 1 -> "one", _ -> "other" } // ============================================================================= // List with rest pattern covers all lengths (empty + rest) // ============================================================================= @test_list_empty_plus_rest tests @list_empty_plus_rest () -> void = { assert_eq(actual: list_empty_plus_rest([]), expected: "empty"); assert_eq(actual: list_empty_plus_rest([1]), expected: "nonempty"); assert_eq(actual: list_empty_plus_rest([1, 2, 3]), expected: "nonempty") } @list_empty_plus_rest (lst: [int]) -> str = match lst { [] -> "empty", [_, ..] -> "nonempty" } // ============================================================================= // List with rest-only pattern (covers everything including empty) // ============================================================================= @test_list_rest_only tests @list_rest_only () -> void = { assert_eq(actual: list_rest_only([]), expected: 0); assert_eq(actual: list_rest_only([1, 2, 3]), expected: 3) } @list_rest_only (lst: [int]) -> int = match lst { [..all] -> all.len() } // ============================================================================= // List with multiple exact lengths + rest // ============================================================================= @test_list_multi_exact_plus_rest tests @list_multi_exact_plus_rest () -> void = { assert_eq(actual: list_multi_exact_plus_rest([]), expected: "none"); assert_eq(actual: list_multi_exact_plus_rest([1]), expected: "one"); assert_eq(actual: list_multi_exact_plus_rest([1, 2]), expected: "many"); assert_eq(actual: list_multi_exact_plus_rest([1, 2, 3, 4, 5]), expected: "many") } @list_multi_exact_plus_rest (lst: [int]) -> str = match lst { [] -> "none", [_] -> "one", [_, _, ..] -> "many" } // ============================================================================= // Never variant omitted from exhaustiveness (uninhabited variant) // ============================================================================= type MaybeNever = Value(v: int) | Impossible(n: Never); @test_never_variant_omittable tests @never_variant_omittable () -> void = { assert_eq(actual: never_variant_omittable(Value(v: 42)), expected: 42) } // Impossible(n: Never) can never be constructed, so omitting it is fine @never_variant_omittable (m: MaybeNever) -> int = match m { Value(v) -> v } // Never variant can still be matched explicitly (not redundant) @test_never_variant_explicit tests @never_variant_explicit () -> void = { assert_eq(actual: never_variant_explicit(Value(v: 10)), expected: 10) } @never_variant_explicit (m: MaybeNever) -> int = match m { Value(v) -> v, Impossible(_) -> 0 } ``` ## tests/spec/patterns/try.ori — `try` blocks and `?` error propagation ```ori // Spec: 10-patterns.md § try // Design: 02-syntax/04-patterns-reference.md § try // // TODO: Type checker needs various features // - `try` pattern for error propagation // - `?` operator for early return on Err // - Result type polymorphism (let-polymorphism in try blocks) // - `is_ok` and `is_err` prelude function polymorphism // - Option.ok_or() method for conversion to Result // // Uncomment when type checker supports these features. // use std.testing { assert, assert_eq, assert_ne } // // // ============================================================================= // // try Pattern (Error Propagation) // // ============================================================================= // // NOTE: Generic type syntax Result requires Phase 6. // // Using type inference for now (return types are inferred from Ok/Err usage). // // Helper functions that return Result (type inferred) // @succeed (x: int) = Ok(x) // // @fail_if_zero (x: int) = { // if x == 0 then Err("zero not allowed") else Ok(x), // } // // @double_if_positive (x: int) = { // if x > 0 then Ok(x * 2) else Err("must be positive"), // } // // @try_success () = try { // let x = succeed(5), // let y = succeed(10), // Ok(x + y), // } // // @try_early_return () = try { // let x = fail_if_zero(0), // let y = succeed(10), // Ok(x + y), // } // // @try_chain (n: int) = try { // let a = fail_if_zero(n), // let b = double_if_positive(a), // Ok(b), // } // // @try_with_transform (x: int) = try { // let a = succeed(x), // let doubled = double_if_positive(a), // let tripled = succeed(doubled + a), // Ok(tripled), // } // // // ============================================================================= // // try with ? operator // // ============================================================================= // @parse_int (s: str) -> Result = match s { // "1" -> Ok(1), // "2" -> Ok(2), // "3" -> Ok(3), // _ -> Err("invalid number"), // } // // @try_question_mark (a: str, b: str) -> Result = try { // let x = parse_int(s: a)?, // let y = parse_int(s: b)?, // Ok(x + y), // } // // // ============================================================================= // // try with nested calls // // ============================================================================= // @outer_op (x: int) -> Result = try { // let inner = inner_op(x)?, // Ok(inner * 2), // } // // @inner_op (x: int) -> Result = { // if x > 0 then Ok(x) else Err("must be positive"), // } // // @try_nested () -> Result = outer_op(5) // // // ============================================================================= // // try with Option // // ============================================================================= // @find_value (key: str) -> Option = match key { // "a" -> Some(1), // "b" -> Some(2), // _ -> None, // } // // @try_with_option (k1: str, k2: str) -> Result = try { // let v1 = find_value(key: k1).ok_or(error: "missing k1")?, // let v2 = find_value(key: k2).ok_or(error: "missing k2")?, // Ok(v1 + v2), // } // // @try_final_expr (x: int) -> Result = try { // let doubled = succeed(x: x * 2)?, // Ok(doubled), // } // // @try_complex (n: int) -> Result<[int], str> = try { // let a = fail_if_zero(x: n)?, // let b = double_if_positive(x: a)?, // let c = succeed(x: b + 1)?, // Ok([a, b, c]), // } // // // The second call should not execute because first fails // // Should not reach here // @try_short_circuit () -> Result = try { // let _ = fail_if_zero(x: 0)?, // Ok("done"), // } // // // ============================================================================= // // Basic try tests // // ============================================================================= // @test_try_success tests @try_success () -> void = { // let result = try_success(), // assert(cond: is_ok(r: result)), // } // // @test_try_early_return tests @try_early_return () -> void = { // let result = try_early_return(), // assert(cond: is_err(r: result)), // } // // // This should not execute // @test_try_chain tests @try_chain () -> void = { // let result = try_chain(5), // assert(cond: is_ok(r: result)), // } // // @test_try_chain_fail tests @try_chain_fail () -> void = { // let result = try_chain(0), // assert(cond: is_err(r: result)), // } // // // ============================================================================= // // try with transformations // // ============================================================================= // @test_try_with_transform tests @try_with_transform () -> void = { // let result = try_with_transform(3), // assert(cond: is_ok(r: result)), // } // // @test_try_question_mark tests @try_question_mark () -> void = { // let result = try_question_mark("1", "2"), // assert(cond: is_ok(r: result)), // } // // @test_try_question_mark_err tests @try_question_mark_err () -> void = { // let result = try_question_mark("1", "invalid"), // assert(cond: is_err(r: result)), // } // // @test_try_nested tests @try_nested () -> void = { // let result = outer_op(5), // assert(cond: is_ok(r: result)), // } // // @test_try_nested_err tests @try_nested_err () -> void = { // let result = outer_op(-1), // assert(cond: is_err(r: result)), // } // // // Using try with Option requires converting to Result // @test_try_with_option tests @try_with_option () -> void = { // let result = try_with_option("a", "b"), // assert(cond: is_ok(r: result)), // } // // // ============================================================================= // // try returning the final expression // // ============================================================================= // @test_try_final_expr tests @try_final_expr () -> void = { // let result = try_final_expr(10), // match result { // Ok(n) -> assert_eq(actual: n, expected: 20), // Err(_) -> assert(cond: false), // }, // } // // // ============================================================================= // // try with complex expressions // // ============================================================================= // @test_try_complex tests @try_complex () -> void = { // let result = try_complex(5), // assert(cond: is_ok(r: result)), // } // // // ============================================================================= // // try with early return on first error // // ============================================================================= // @test_try_short_circuit tests @try_short_circuit () -> void = { // let result = try { // let _ = fail_if_zero(x: 0)?, // let _ = succeed(x: 100)?, // Ok("done"), // }, // assert(cond: is_err(r: result)), // } ``` ## tests/spec/patterns/catch.ori — `catch` for panic-to-Result conversion ```ori // Spec: 20-errors-and-panics.md § Catching Panics // Tests for the catch(expr: ...) pattern that converts panics to Result. use std.testing { assert_eq, assert } // ============================================================================= // catch — Panic Recovery // ============================================================================= // catch returns Ok when expression succeeds @test_catch_success tests @catch_success () -> void = { let result = catch(expr: 42); match result { Ok(v) -> assert_eq(actual: v, expected: 42), Err(_) -> panic(msg: "expected Ok") } } @catch_success () -> int = 42; // catch returns Err when expression panics @test_catch_panic tests @catch_panic () -> void = { let result = catch(expr: panic(msg: "test error")); match result { Err(_) -> (), Ok(_) -> panic(msg: "expected Err") } } @catch_panic () -> int = 1; // catch captures panic message @test_catch_message tests @catch_message () -> void = { let result = catch(expr: panic(msg: "specific error")); match result { Err(m) -> assert_eq( actual: m, expected: "specific error", ), Ok(_) -> panic(msg: "expected Err") } } @catch_message () -> str = { let result = catch(expr: panic(msg: "specific error")); match result { Err(m) -> m, Ok(_) -> "no error" } } // catch captures division by zero @test_catch_div_zero tests @catch_div_zero () -> void = { let result = catch(expr: 1 / 0); match result { Err(_) -> (), Ok(_) -> panic(msg: "expected Err from div by zero") } } @catch_div_zero () -> int = 1; // catch wraps successful value in Ok @test_catch_ok_value tests @catch_ok_value () -> void = { let result = catch(expr: 10 + 20); match result { Ok(v) -> assert_eq( actual: v, expected: 30, ), Err(_) -> panic(msg: "expected Ok") } } @catch_ok_value () -> int = 10 + 20; // catch works with string expressions @test_catch_string tests @catch_string () -> void = { let result = catch(expr: "hello" + " world"); match result { Ok(v) -> assert_eq(actual: v, expected: "hello world"), Err(_) -> panic(msg: "expected Ok") } } @catch_string () -> str = "hello" + " world"; // catch propagates non-panic errors (ControlAction) unchanged // Nested catch: inner catches inner panic, outer catches outer panic @test_catch_nested tests @catch_nested () -> void = { let outer = catch( expr: { let inner = catch(expr: panic(msg: "inner")); match inner { Err(m) -> assert_eq(actual: m, expected: "inner"), Ok(_) -> panic(msg: "expected inner Err") } }, ); match outer { Ok(_) -> (), Err(m) -> panic(msg: "outer should be Ok, got: " + m) } } @catch_nested () -> int = 1; ``` ## tests/spec/patterns/recurse.ori — `recurse` structured-recursion pattern ```ori // Spec: 10-patterns.md § recurse // Design: 02-syntax/04-patterns-reference.md § recurse use std.testing { assert, assert_eq, assert_ne } // ============================================================================= // recurse Pattern (Recursive Functions) // ============================================================================= @test_factorial tests @factorial () -> void = { assert_eq( actual: factorial(0), expected: 1, ); assert_eq( actual: factorial(1), expected: 1, ); assert_eq( actual: factorial(5), expected: 120, ) } @factorial (n: int) -> int = recurse( condition: n <= 1, base: 1, step: n * self(n - 1), ); @test_fibonacci tests @fibonacci () -> void = { assert_eq( actual: fibonacci(0), expected: 0, ); assert_eq( actual: fibonacci(1), expected: 1, ); assert_eq( actual: fibonacci(10), expected: 55, ) } @fibonacci (n: int) -> int = recurse( condition: n <= 1, base: n, step: self(n - 1) + self(n - 2), memo: true, ); @test_sum_to_n tests @sum_to_n () -> void = { assert_eq( actual: sum_to_n(0), expected: 0, ); assert_eq( actual: sum_to_n(5), expected: 15, ); assert_eq( actual: sum_to_n(10), expected: 55, ) } @sum_to_n (n: int) -> int = recurse( condition: n <= 0, base: 0, step: n + self(n - 1), ); @test_power tests @power () -> void = { assert_eq( actual: power( base: 2, exp: 0, ), expected: 1, ); assert_eq( actual: power( base: 2, exp: 10, ), expected: 1024, ); assert_eq( actual: power( base: 3, exp: 4, ), expected: 81, ) } @power (base: int, exp: int) -> int = recurse( condition: exp <= 0, base: 1, step: base * self( base:, exp: exp - 1, ), ); // ============================================================================= // Parallel Recursion (stub - executes sequentially) // ============================================================================= @test_fib_parallel tests @fib_parallel () -> void = { assert_eq( actual: fib_parallel(0), expected: 0, ); assert_eq( actual: fib_parallel(1), expected: 1, ); assert_eq( actual: fib_parallel(10), expected: 55, ) } // parallel: 5 means parallelize recursive calls when n > 5 // Currently executes sequentially (stub) but syntax is accepted @fib_parallel (n: int) -> int = recurse( condition: n <= 1, base: n, step: self(n - 1) + self(n - 2), parallel: 5, ); // ============================================================================= // Memoization Tests // ============================================================================= @test_fib_memo tests @fib_memo () -> void = { // Without memo, this would be exponential time // With memo, it's linear assert_eq(actual: fib_memo(20), expected: 6765) } @fib_memo (n: int) -> int = recurse( condition: n <= 1, base: n, step: self(n - 1) + self(n - 2), memo: true, ); @test_fib_large tests @fib_large () -> void = { // Test larger fibonacci that would be impossible without memoization let result = fib_memo(30); assert_eq(actual: result, expected: 832040) } // ============================================================================= // Multiple Parameters in Self Call // ============================================================================= @test_gcd tests @gcd () -> void = { assert_eq(actual: gcd(a: 48, b: 18), expected: 6); assert_eq(actual: gcd(a: 100, b: 25), expected: 25); assert_eq(actual: gcd(a: 17, b: 13), expected: 1) } @gcd (a: int, b: int) -> int = recurse( condition: b == 0, base: a, step: self(a: b, b: a % b), ); @test_ackermann tests @ackermann () -> void = { // Ackermann function - tests deep recursion with memo assert_eq(actual: ackermann(m: 0, n: 0), expected: 1); assert_eq(actual: ackermann(m: 1, n: 1), expected: 3); assert_eq(actual: ackermann(m: 2, n: 2), expected: 7) } @ackermann (m: int, n: int) -> int = recurse( condition: m == 0, base: n + 1, step: if n == 0 then self(m: m - 1, n: 1) else self(m: m - 1, n: self(m:, n: n - 1)), memo: true, ); // ============================================================================= // Tail Recursion Optimization // ============================================================================= @test_sum_tail tests @sum_tail () -> void = { // Tail-optimized should handle large N without stack overflow assert_eq(actual: sum_tail(n: 100, acc: 0), expected: 5050) } @sum_tail (n: int, acc: int) -> int = recurse( condition: n == 0, base: acc, step: self(n: n - 1, acc: acc + n), ); @test_factorial_tail tests @factorial_tail () -> void = { assert_eq(actual: factorial_tail(n: 5, acc: 1), expected: 120); assert_eq(actual: factorial_tail(n: 10, acc: 1), expected: 3628800) } @factorial_tail (n: int, acc: int) -> int = recurse( condition: n <= 1, base: acc, step: self(n: n - 1, acc: acc * n), ); // ============================================================================= // Recurse with List Processing // ============================================================================= @test_list_sum tests @list_sum () -> void = { assert_eq(actual: list_sum(items: [1, 2, 3, 4, 5], idx: 0), expected: 15); assert_eq(actual: list_sum(items: [], idx: 0), expected: 0) } @list_sum (items: [int], idx: int) -> int = recurse( condition: idx >= items.len(), base: 0, step: items[idx] + self(items:, idx: idx + 1), ); @test_list_max tests @list_max () -> void = { assert_eq(actual: list_max(items: [3, 1, 4, 1, 5, 9, 2, 6], idx: 0, current: 0), expected: 9) } @list_max (items: [int], idx: int, current: int) -> int = recurse( condition: idx >= items.len(), base: current, step: self( items:, idx: idx + 1, current: if items[idx] > current then items[idx] else current, ), ); // ============================================================================= // Recurse with String Processing // ============================================================================= @test_count_char tests @count_char () -> void = { assert_eq(actual: count_char(s: "hello", c: 'l', idx: 0), expected: 2); assert_eq(actual: count_char(s: "hello", c: 'x', idx: 0), expected: 0) } @count_char (s: str, c: char, idx: int) -> int = recurse( condition: idx >= s.len(), base: 0, step: { let count = if s[idx] == str(c) then 1 else 0; count + self(s:, c:, idx: idx + 1) }, ); // ============================================================================= // Recurse with Complex Conditions // ============================================================================= @test_binary_search tests @binary_search () -> void = { let items = [1, 3, 5, 7, 9, 11, 13, 15]; assert_eq(actual: binary_search(items:, target: 7, lo: 0, hi: items.len() - 1), expected: 3); assert_eq(actual: binary_search(items:, target: 1, lo: 0, hi: items.len() - 1), expected: 0); assert_eq(actual: binary_search(items:, target: 15, lo: 0, hi: items.len() - 1), expected: 7); assert_eq(actual: binary_search(items:, target: 6, lo: 0, hi: items.len() - 1), expected: -1) } @binary_search (items: [int], target: int, lo: int, hi: int) -> int = recurse( condition: lo > hi, base: -1, step: { let mid = (lo + hi) div 2; let mid_val = items[mid]; if mid_val == target then mid else if mid_val < target then self(items:, target:, lo: mid + 1, hi:) else self(items:, target:, lo:, hi: mid - 1) }, ); // ============================================================================= // Recurse Returning Collections // ============================================================================= @test_range_list tests @range_list () -> void = { let result = range_list(start: 1, end: 5); assert_eq(actual: result, expected: [1, 2, 3, 4, 5]) } // NOTE: List spread in recurse step may not be supported // Simplified to use for/yield @range_list (start: int, end: int) -> [int] = for i in start..=end yield i; // ============================================================================= // Recurse with Boolean Condition // ============================================================================= @test_is_even tests @is_even () -> void = { assert(cond: is_even(n: 0)); assert(cond: is_even(n: 2)); assert(cond: is_even(n: 100)); assert(cond: !is_even(n: 1)); assert(cond: !is_even(n: 99)) } @is_even (n: int) -> bool = recurse( condition: n == 0, base: true, step: !self(n - 1), ); // ============================================================================= // Mutual Recursion via Recurse // ============================================================================= // Note: True mutual recursion requires two separate functions // This tests indirect recursion through a helper @test_countdown tests @countdown () -> void = { let result = countdown(n: 5); assert_eq(actual: result, expected: "5 4 3 2 1 0") } @countdown (n: int) -> str = recurse( condition: n < 0, base: "", step: str(n) + if n > 0 then " " + self(n - 1) else "", ); ``` ## tests/spec/types/const_generics.ori — const generics (`@f`) ```ori // Spec: const generics type checker support // Tests that const generic parameters are tracked and validated by the type checker. // // STATUS: Parser [OK], Type Checker [OK for body-level const params] // Const evaluation and call-site validation not yet implemented. // ============================================================================= // Const Generic Parameters in Function Declarations // ============================================================================= // Function with const generic parameter @const_param<$N: int> () -> int = N; // Function with const generic parameter and default value @const_param_default<$N: int = 10> () -> int = N; // Multiple const params @multi_const<$A: int, $B: int> () -> int = A + B; // Mixed type and const params @mixed_params (x: T) -> T = x; // Bool const param @bool_param<$F: bool> () -> bool = F; // Const param in arithmetic expression @add_one<$N: int> () -> int = N + 1; // ============================================================================= // Const Expressions in Type Arguments // ============================================================================= // Integer literal in type argument position #skip("const generics: Array type not yet implemented") @int_type_arg () -> Array = []; // Const param reference in type argument position #skip("const generics: Array type not yet implemented") @const_type_arg<$N: int> () -> Array = []; // ============================================================================= // Fixed-Capacity Lists with Const Expressions // ============================================================================= // Fixed list with integer literal capacity (existing syntax) #skip("const generics: fixed-capacity lists not yet implemented (Section 18.2)") @fixed_list_literal () -> [int, max 10] = []; // Fixed list with const param capacity (new syntax) #skip("const generics: fixed-capacity lists not yet implemented (Section 18.2)") @fixed_list_const<$N: int> () -> [int, max $N] = []; // ============================================================================= // Where Clause Const Bounds // ============================================================================= // Const bound in where clause #skip("const generics: const bounds not yet implemented (Section 18.5)") @const_bound<$N: int> () -> int where N > 0 = N; // Mixed type and const bounds #skip("const generics: const bounds not yet implemented (Section 18.5)") @mixed_bounds (x: T) -> T where T: Clone, N > 0 = x; ``` ## tests/spec/types/existential.ori — existential types (`impl Trait` returns) ```ori // Spec: 06-types.md § Existential Types // Tests for impl Trait, impl Trait + Bound, with where clauses use std.testing { assert, assert_eq } // ============================================================================= // Basic impl Trait Return Type // ============================================================================= // NOTE: impl Trait may not be fully implemented yet. // These tests are designed to expose if the feature doesn't work. // Simple impl Iterator // @test_impl_iterator tests @make_numbers () -> void = { // let iter = make_numbers(), // // iter should be usable as an Iterator // let (first, _) = iter.next(), // assert(cond: is_some(opt: first)), // } // @make_numbers () -> impl Iterator where Item == int = [1, 2, 3].iter() // ============================================================================= // impl Trait with Multiple Bounds // ============================================================================= // @test_impl_multi_bounds tests @clonable_printable () -> void = { // let val = clonable_printable(), // let cloned = val.clone(), // assert(cond: true), // } // @clonable_printable () -> impl Clone + Printable = 42 // ============================================================================= // impl Trait with Where Clause // ============================================================================= // @test_impl_with_where tests @iter_with_where () -> void = { // let iter = iter_with_where([1, 2, 3]), // assert(cond: true), // } // @iter_with_where (items: [int]) -> impl Iterator where Item == int = items.iter() // ============================================================================= // Fallback: Test that basic trait objects work // (These use trait objects, not impl Trait, to verify trait basics work) // ============================================================================= // // STATUS: Lexer [OK], Parser [OK], Evaluator [BROKEN] - no .to_str()/.clone() on primitives // Primitives (int, str, etc.) do not have trait methods registered in the evaluator. @test_printable_object tests @printable_object () -> void = { let s = printable_value(); assert_eq(actual: s, expected: "42") } @printable_value () -> str = 42.to_str(); @test_clone_trait tests @clone_trait () -> void = { let original = 42; let cloned = original.clone(); assert_eq(actual: cloned, expected: 42) } @clone_trait () -> int = { let x = 100; x.clone() } // ============================================================================= // Trait Object as Parameter (not impl Trait) // ============================================================================= // Trait objects as parameters (different from impl Trait in return) // @test_trait_param tests @take_printable () -> void = { // let result = display_it(item: 42), // assert_eq(actual: result, expected: "42"), // } // @display_it (item: Printable) -> str = item.to_str() // ============================================================================= // Static Dispatch with Generics (alternative to impl Trait) // ============================================================================= @format_generic (item: T) -> str = item.to_str(); @test_generic_printable tests @generic_printable () -> void = { let result = format_generic(item: 123); assert_eq(actual: result, expected: "123") } @generic_printable () -> str = format_generic(item: 123); @test_generic_printable_str tests @generic_printable_str () -> void = { let result = format_generic(item: "hello"); assert_eq(actual: result, expected: "hello") } @generic_printable_str () -> str = format_generic(item: "hello"); // ============================================================================= // Multiple Trait Bounds on Generic // ============================================================================= @clone_and_format (item: T) -> (T, str) = { let cloned = item.clone(); let formatted = item.to_str(); (cloned, formatted) } @test_multi_bound_generic tests @multi_bound_generic () -> void = { let (cloned, formatted) = clone_and_format(item: 42); assert_eq(actual: cloned, expected: 42); assert_eq(actual: formatted, expected: "42") } @multi_bound_generic () -> (int, str) = clone_and_format(item: 42); // ============================================================================= // Returning Iterator via Generic (not impl Trait) // ============================================================================= // This tests iterator creation works even if impl Trait doesn't @test_iter_from_list tests @iter_from_list () -> void = { let items = [1, 2, 3]; let doubled = items.map(transform: (x: int) -> int = x * 2); assert_eq(actual: doubled, expected: [2, 4, 6]) } @iter_from_list () -> [int] = [1, 2, 3].map(transform: (x: int) -> int = x * 2); @test_iter_chain tests @iter_chain () -> void = { let mapped: [int] = [1, 2, 3].map(transform: (x: int) -> int = x * 2); let result = mapped.filter(predicate: (x: int) -> bool = x > 2); assert_eq(actual: result, expected: [4, 6]) } @iter_chain () -> [int] = { let mapped: [int] = [1, 2, 3].map(transform: (x: int) -> int = x * 2); mapped.filter(predicate: (x: int) -> bool = x > 2) } // ============================================================================= // Opaque Type Semantics Test // ============================================================================= // The key property of impl Trait is that the concrete type is hidden. // Testing with generic wrapper to verify type erasure semantics. type Opaque = { inner: T } @wrap_value (x: T) -> Opaque = Opaque { inner: x } @test_opaque_wrapper tests @opaque_wrapper () -> void = { let wrapped = wrap_value(x: 42); assert_eq(actual: wrapped.inner, expected: 42) } @opaque_wrapper () -> int = { let wrapped = wrap_value(x: 42); wrapped.inner } ``` ## tests/spec/inference/polymorphism.ori — Hindley-Milner inference + polymorphism ```ori // Spec: 06-types.md § Type Inference // Design: 03-type-system/05-type-inference.md § Generalization, Instantiation use std.testing { assert, assert_eq, assert_ne } // ============================================================================= // Let-Polymorphism (Generalization and Instantiation) // ============================================================================= @test_let_poly_identity tests @let_poly_identity () -> void = { // The identity function should work with different types let id = x -> x; let int_result = id(x: 42); let str_result = id(x: "hello"); assert_eq( actual: int_result, expected: 42, ); assert_eq( actual: str_result, expected: "hello", ) } @let_poly_identity () -> int = { let id = x -> x; id(x: 42) } @test_let_poly_const tests @let_poly_const () -> void = { // A function that ignores its second argument let const_fn = (a, b) -> a; let result1 = const_fn( a: 1, b: "ignored", ); let result2 = const_fn( a: "kept", b: 999, ); assert_eq( actual: result1, expected: 1, ); assert_eq( actual: result2, expected: "kept", ) } @let_poly_const () -> int = { let const_fn = (a, b) -> a; const_fn( a: 42, b: "ignored", ) } // ============================================================================= // Polymorphism with Collections // ============================================================================= @test_poly_list_head tests @poly_list_head () -> void = { // A function that gets the first element let head = xs -> xs[0]; let int_head = head(xs: [1, 2, 3]); let str_head = head(xs: ["a", "b", "c"]); assert_eq( actual: int_head, expected: 1, ); assert_eq( actual: str_head, expected: "a", ) } @poly_list_head () -> int = { let head = xs -> xs[0]; head(xs: [1, 2, 3]) } @test_poly_list_length tests @poly_list_length () -> void = { // Length works on any list type let int_len = len(collection: [1, 2, 3]); let str_len = len(collection: ["a", "b"]); assert_eq( actual: int_len, expected: 3, ); assert_eq( actual: str_len, expected: 2, ) } @poly_list_length () -> int = len(collection: [1, 2, 3]); // ============================================================================= // Polymorphism with Option/Result // ============================================================================= @test_poly_option tests @poly_option () -> void = { let int_opt = Some(42); let str_opt = Some("hello"); assert(cond: is_some(opt: int_opt)); assert(cond: is_some(opt: str_opt)) } @poly_option () -> bool = { let opt = Some(42); is_some(opt: opt) } // ============================================================================= // Instantiation at Call Sites // ============================================================================= @test_instantiate_multiple_calls tests @instantiate_multiple_calls () -> void = { // Each use of a polymorphic function gets fresh type variables let wrap = x -> Some(x); let opt1 = wrap(x: 1); let opt2 = wrap(x: "str"); let opt3 = wrap(x: true); assert(cond: is_some(opt: opt1)); assert(cond: is_some(opt: opt2)); assert(cond: is_some(opt: opt3)) } @instantiate_multiple_calls () -> bool = { let wrap = x -> Some(x); is_some(opt: wrap(x: 42)) } // ============================================================================= // Type Inference Flow // ============================================================================= @test_inference_flow_down tests @inference_flow_down () -> void = { // Return type flows down to guide inference let result: str = if true then "yes" else "no"; assert_eq( actual: result, expected: "yes", ) } @inference_flow_down () -> str = if true then "yes" else "no"; @test_inference_flow_up tests @inference_flow_up () -> void = { // Inferred types flow up let x = 1; let y = x + 1; let z = y * 2; assert_eq( actual: z, expected: 4, ) } @inference_flow_up () -> int = { let x = 1; let y = x + 1; y * 2 } ``` ## tests/spec/expressions/operators_precedence.ori — operator precedence in practice ```ori // Spec: 09-expressions.md § Operator Precedence // Tests for all precedence levels interacting correctly use std.testing { assert, assert_eq } // ============================================================================= // Precedence Level 1: Postfix (. [] () ? as as?) // ============================================================================= @test_postfix_highest tests @postfix_highest () -> void = { // Postfix binds tighter than everything let list = [1, 2, 3]; // -list[0] is -(list[0]) not (-list)[0] assert_eq(actual: -list[0], expected: -1) } @postfix_highest () -> int = { let list = [1, 2, 3]; -list[0] } type Point = { x: int, y: int } @test_field_access_binds_tight tests @field_access_binds_tight () -> void = { let p = Point { x: 10, y: 20 }; // -p.x is -(p.x), not (-p).x assert_eq(actual: -p.x, expected: -10) } @field_access_binds_tight () -> int = { let p = Point { x: 10, y: 20 }; -p.x } @test_method_call_binds_tight tests @method_call_binds_tight () -> void = { let list = [1, 2, 3]; // -list.len() is -(list.len()), not (-list).len() assert_eq(actual: -list.len(), expected: -3) } @method_call_binds_tight () -> int = { let list = [1, 2, 3]; -list.len() } // ============================================================================= // Precedence Level 2: Unary (! - ~) // ============================================================================= @test_unary_before_mul tests @unary_before_mul () -> void = { // -2 * 3 is (-2) * 3 = -6, not -(2 * 3) assert_eq(actual: unary_before_mul(), expected: -6) } @unary_before_mul () -> int = -2 * 3; @test_not_before_and tests @not_before_and () -> void = { // !false && true is (!false) && true = true && true = true assert(cond: !false && true) } @not_before_and () -> bool = !false && true; @test_bitnot_before_bitand tests @bitnot_before_bitand () -> void = { // ~0 & 0xFF is (~0) & 0xFF = -1 & 0xFF = 0xFF = 255 assert_eq(actual: bitnot_before_bitand(), expected: 255) } @bitnot_before_bitand () -> int = ~0 & 0xFF; // ============================================================================= // Precedence Level 3: Multiplicative (* / % div) // ============================================================================= @test_mul_before_add tests @mul_before_add () -> void = { // 2 + 3 * 4 = 2 + 12 = 14 assert_eq(actual: mul_before_add(), expected: 14) } @mul_before_add () -> int = 2 + 3 * 4; @test_div_before_sub tests @div_before_sub () -> void = { // 10 - 6 / 2 = 10 - 3 = 7 assert_eq(actual: div_before_sub(), expected: 7) } @div_before_sub () -> int = 10 - 6 / 2; @test_mod_before_add tests @mod_before_add () -> void = { // 10 + 7 % 3 = 10 + 1 = 11 assert_eq(actual: mod_before_add(), expected: 11) } @mod_before_add () -> int = 10 + 7 % 3; @test_floor_div_before_add tests @floor_div_before_add () -> void = { // 10 + 7 div 3 = 10 + 2 = 12 assert_eq(actual: floor_div_before_add(), expected: 12) } @floor_div_before_add () -> int = 10 + 7 div 3; @test_mul_div_left_assoc tests @mul_div_left_assoc () -> void = { // 100 / 10 * 2 = (100 / 10) * 2 = 10 * 2 = 20 assert_eq(actual: mul_div_left_assoc(), expected: 20) } @mul_div_left_assoc () -> int = 100 / 10 * 2; // ============================================================================= // Precedence Level 4: Additive (+ -) // ============================================================================= @test_add_before_shift tests @add_before_shift () -> void = { // 1 + 1 << 2 = (1 + 1) << 2 = 2 << 2 = 8 assert_eq(actual: add_before_shift(), expected: 8) } @add_before_shift () -> int = 1 + 1 << 2; @test_sub_before_shift tests @sub_before_shift () -> void = { // 5 - 1 >> 1 = (5 - 1) >> 1 = 4 >> 1 = 2 assert_eq(actual: sub_before_shift(), expected: 2) } @sub_before_shift () -> int = 5 - 1 >> 1; @test_add_sub_left_assoc tests @add_sub_left_assoc () -> void = { // 10 - 5 + 3 = (10 - 5) + 3 = 5 + 3 = 8 assert_eq(actual: add_sub_left_assoc(), expected: 8) } @add_sub_left_assoc () -> int = 10 - 5 + 3; // ============================================================================= // Precedence Level 5: Shift (<< >>) // ============================================================================= @test_shift_before_comparison tests @shift_before_comparison () -> void = { // 1 << 2 < 10 is (1 << 2) < 10 = 4 < 10 = true assert(cond: shift_before_comparison()) } @shift_before_comparison () -> bool = 1 << 2 < 10; @test_shift_left_assoc tests @shift_left_assoc () -> void = { // 1 << 2 << 1 = (1 << 2) << 1 = 4 << 1 = 8 assert_eq(actual: shift_left_assoc(), expected: 8) } @shift_left_assoc () -> int = 1 << 2 << 1; // ============================================================================= // Precedence Level 6: Range (.. ..= by) // ============================================================================= @test_range_precedence tests @range_precedence () -> void = { // Range expressions should work with arithmetic let r = for x in 0 + 1..5 + 5 yield x; assert_eq(actual: r.len(), expected: 9) // 1..10 has 9 elements } @range_precedence () -> int = { let r = for x in 0 + 1..5 + 5 yield x; r.len() } // ============================================================================= // Precedence Level 7: Relational (< > <= >=) // ============================================================================= @test_relational_before_equality tests @relational_before_equality () -> void = { // 1 < 2 == true is (1 < 2) == true = true == true = true assert(cond: relational_before_equality()) } @relational_before_equality () -> bool = 1 < 2 == true; @test_relational_chain_not_supported tests @relational_chain_not_supported () -> void = { // In Ori, 1 < 2 < 3 would parse as (1 < 2) < 3 = true < 3 // But this should be a type error (bool < int) // Instead, use: 1 < 2 && 2 < 3 assert(cond: 1 < 2 && 2 < 3) } @relational_chain_not_supported () -> bool = 1 < 2 && 2 < 3; // ============================================================================= // Precedence Level 8: Equality (== !=) // ============================================================================= @test_equality_before_bitand tests @equality_before_bitand () -> void = { // This is unusual: a & b == 0 is a & (b == 0) // Most languages have bitwise higher than equality // In Ori's spec, equality is higher than bitand // So: 5 & 3 == 1 is 5 & (3 == 1) = 5 & false -> type error // Actually let's check: spec says bitand (9) < equality (8) // So equality binds tighter: 5 & 3 == 1 = 5 & (3 == 1) // This would be a type error, so we need parens assert(cond: (5 & 3) == 1) } @equality_before_bitand () -> bool = (5 & 3) == 1; @test_equality_left_assoc tests @equality_left_assoc () -> void = { // 1 == 1 == true = (1 == 1) == true = true == true = true assert(cond: equality_left_assoc()) } @equality_left_assoc () -> bool = 1 == 1 == true; // ============================================================================= // Precedence Level 9: Bitwise AND (&) // ============================================================================= @test_bitand_before_bitxor tests @bitand_before_bitxor () -> void = { // 0xFF & 0x0F ^ 0xF0 = (0xFF & 0x0F) ^ 0xF0 = 0x0F ^ 0xF0 = 0xFF = 255 assert_eq(actual: bitand_before_bitxor(), expected: 255) } @bitand_before_bitxor () -> int = 0xFF & 0x0F ^ 0xF0; // ============================================================================= // Precedence Level 10: Bitwise XOR (^) // ============================================================================= @test_bitxor_before_bitor tests @bitxor_before_bitor () -> void = { // 0xFF | 0x0F ^ 0xFF = 0xFF | (0x0F ^ 0xFF) = 0xFF | 0xF0 = 0xFF = 255 assert_eq(actual: bitxor_before_bitor(), expected: 255) } @bitxor_before_bitor () -> int = 0xFF | 0x0F ^ 0xFF; // ============================================================================= // Precedence Level 11: Bitwise OR (|) // ============================================================================= @test_bitor_before_and tests @bitor_before_and () -> void = { // 0 | 1 && true = (0 | 1) && true -> but 0|1 = 1, and we need bool // Actually this would need to compare, let's do: // (0 | 1) > 0 && true = 1 > 0 && true = true && true = true assert(cond: (0 | 1) > 0 && true) } @bitor_before_and () -> bool = (0 | 1) > 0 && true; // ============================================================================= // Precedence Level 12: Logical AND (&&) // ============================================================================= @test_and_before_or tests @and_before_or () -> void = { // false && true || true = (false && true) || true = false || true = true assert(cond: and_before_or()) } @and_before_or () -> bool = false && true || true; @test_and_left_assoc tests @and_left_assoc () -> void = { // true && false && true = (true && false) && true = false && true = false assert(cond: !(true && false && true)) } @and_left_assoc () -> bool = true && false && true; // ============================================================================= // Precedence Level 13: Logical OR (||) // ============================================================================= @test_or_before_coalesce tests @or_before_coalesce () -> void = { // Logical OR has lower precedence than most, but higher than ?? let opt: Option = Some(false); // This tests that || is evaluated before ?? // (opt ?? true) || false: if opt is Some(false), it's false || false = false // But with correct precedence: opt ?? (true || false) = Some(false) (opt used) // Actually ?? returns unwrapped value if Some, not the Option // So opt ?? (true || false) = false (the Some value) // Let's do a simpler test let a = true || false; let b = a || false; assert(cond: b) } @or_before_coalesce () -> bool = true || false; @test_or_left_assoc tests @or_left_assoc () -> void = { // false || false || true = (false || false) || true = false || true = true assert(cond: or_left_assoc()) } @or_left_assoc () -> bool = false || false || true; // ============================================================================= // Precedence Level 14: Coalesce (??) // ============================================================================= // // STATUS: Lexer [OK], Parser [OK], Evaluator [BROKEN] - ?? operator not implemented // See tests/spec/expressions/coalesce.ori for full details. @test_coalesce_lowest tests @coalesce_lowest () -> void = { // ?? has lowest precedence let opt: Option = Some(5); // opt ?? 10 + 5 = opt ?? (10 + 5) = opt ?? 15 // Since opt is Some(5), result is 5 assert_eq(actual: opt ?? 10 + 5, expected: 5) } @coalesce_lowest () -> int = { let opt: Option = Some(5); opt ?? 10 + 5 } @test_coalesce_with_none tests @coalesce_with_none () -> void = { let opt: Option = None; // opt ?? 10 + 5 = None ?? 15 = 15 assert_eq(actual: opt ?? 10 + 5, expected: 15) } @coalesce_with_none () -> int = { let opt: Option = None; opt ?? 10 + 5 } @test_coalesce_chain tests @coalesce_chain () -> void = { let a: Option = None; let b: Option = None; let c: Option = Some(42); // a ?? b ?? c ?? 0 evaluates left-to-right // None ?? None ?? Some(42) ?? 0 = 42 assert_eq(actual: a ?? b ?? c ?? 0, expected: 42) } @coalesce_chain () -> int = { let a: Option = None; let b: Option = None; let c: Option = Some(42); a ?? b ?? c ?? 0 } // ============================================================================= // Complex Mixed Precedence // ============================================================================= @test_complex_arithmetic tests @complex_arithmetic () -> void = { // 2 + 3 * 4 - 10 / 2 + 1 // = 2 + 12 - 5 + 1 // = 14 - 5 + 1 // = 9 + 1 // = 10 assert_eq(actual: complex_arithmetic(), expected: 10) } @complex_arithmetic () -> int = 2 + 3 * 4 - 10 / 2 + 1; @test_complex_logical tests @complex_logical () -> void = { // !false && true || false && !true // = true && true || false && false // = true || false // = true assert(cond: complex_logical()) } @complex_logical () -> bool = !false && true || false && !true; @test_complex_bitwise tests @complex_bitwise () -> void = { // ~0 & 0xFF | 0x100 ^ 0x0FF // = (-1 & 0xFF) | (0x100 ^ 0x0FF) // = 0xFF | 0x1FF // = 0x1FF = 511 assert_eq(actual: complex_bitwise(), expected: 511) } @complex_bitwise () -> int = ~0 & 0xFF | 0x100 ^ 0x0FF; @test_parens_override_all tests @parens_override_all () -> void = { // Parentheses always override precedence assert_eq(actual: (2 + 3) * 4, expected: 20); assert_eq(actual: 2 + (3 * 4), expected: 14); assert(cond: (true || false) && false == false) } @parens_override_all () -> int = (2 + 3) * 4; // ============================================================================= // Unary and Binary Interaction // ============================================================================= @test_unary_binary_mix tests @unary_binary_mix () -> void = { // -2 + -3 = (-2) + (-3) = -5 assert_eq(actual: unary_binary_mix(), expected: -5) } @unary_binary_mix () -> int = -2 + -3; @test_double_unary tests @double_unary () -> void = { // --5 is -(-5) = 5 assert_eq(actual: double_unary(), expected: 5) } @double_unary () -> int = --5; @test_not_comparison tests @not_comparison () -> void = { // !(5 > 3) vs !5 > 3 // !5 would be type error (! only for bool) // So: !(5 > 3) = !true = false assert(cond: !(5 > 3) == false) } @not_comparison () -> bool = !(5 > 3); // ============================================================================= // Practical Expressions // ============================================================================= @test_bounds_check tests @bounds_check () -> void = { let x = 5; let in_bounds = x >= 0 && x < 10; assert(cond: in_bounds) } @bounds_check () -> bool = { let x = 5; x >= 0 && x < 10 } @test_flag_check tests @flag_check () -> void = { let flags = 0b1010; let flag_set = (flags & 0b0010) != 0; assert(cond: flag_set) } @flag_check () -> bool = { let flags = 0b1010; (flags & 0b0010) != 0 } @test_default_value tests @default_value () -> void = { let opt: Option = None; let value = opt ?? 0 + 100; // This is opt ?? (0 + 100) = opt ?? 100 = 100 assert_eq(actual: value, expected: 100) } @default_value () -> int = { let opt: Option = None; opt ?? 0 + 100 } ``` ## tests/spec/collections/cow/map_cow.ori — value-semantic collections (copy-on-write maps) ```ori // COW (Copy-on-Write) behavioral equivalence tests for maps. // Verifies that map mutations produce correct results and // that shared owners are not affected by COW operations. use std.testing { assert_eq } // --- Insert unique owner --- @insert_unique () -> {str: int} = { {"a": 1}.insert("b", 2) } @test_insert_unique tests @insert_unique () -> void = { assert_eq(actual: insert_unique().len(), expected: 2) } // --- Insert shared owner (COW) --- @insert_shared () -> ({str: int}, {str: int}) = { let base = {"x": 10, "y": 20}; let fork = base.insert("z", 30); (base, fork) } @test_insert_shared tests @insert_shared () -> void = { let (original, modified) = insert_shared(); assert_eq(actual: original.len(), expected: 2); assert_eq(actual: modified.len(), expected: 3); assert_eq(actual: modified.get("z"), expected: Some(30)) } // --- Insert overwrite (shared) --- @insert_overwrite () -> ({str: int}, {str: int}) = { let a = {"a": 1, "b": 2}; let b = a.insert("a", 99); (a, b) } @test_insert_overwrite tests @insert_overwrite () -> void = { let (original, modified) = insert_overwrite(); assert_eq(actual: original.get("a"), expected: Some(1)); assert_eq(actual: modified.get("a"), expected: Some(99)) } // --- Remove unique owner --- @remove_unique () -> {str: int} = { {"a": 1, "b": 2, "c": 3}.remove("b") } @test_remove_unique tests @remove_unique () -> void = { let m = remove_unique(); assert_eq(actual: m.len(), expected: 2); assert_eq(actual: m.contains_key("b"), expected: false) } // --- Remove shared owner (COW) --- @remove_shared () -> ({str: int}, {str: int}) = { let a = {"a": 1, "b": 2}; let b = a.remove("a"); (a, b) } @test_remove_shared tests @remove_shared () -> void = { let (original, modified) = remove_shared(); assert_eq(actual: original.len(), expected: 2); assert_eq(actual: original.contains_key("a"), expected: true); assert_eq(actual: modified.len(), expected: 1); assert_eq(actual: modified.contains_key("a"), expected: false) } // --- Multi-fork --- @multi_fork () -> ({str: int}, {str: int}, {str: int}, {str: int}) = { let base = {"k": 42}; let f1 = base.insert("a", 1); let f2 = base.insert("b", 2); let f3 = base.remove("k"); (base, f1, f2, f3) } @test_multi_fork tests @multi_fork () -> void = { let (base, f1, f2, f3) = multi_fork(); assert_eq(actual: base.len(), expected: 1); assert_eq(actual: f1.len(), expected: 2); assert_eq(actual: f2.len(), expected: 2); assert_eq(actual: f3.len(), expected: 0) } // --- Chained operations --- @chained_ops () -> {str: int} = { {"a": 1}.insert("b", 2).insert("c", 3).remove("a") } @test_chained_ops tests @chained_ops () -> void = { let m = chained_ops(); assert_eq(actual: m.len(), expected: 2); assert_eq(actual: m.contains_key("a"), expected: false); assert_eq(actual: m.get("b"), expected: Some(2)); assert_eq(actual: m.get("c"), expected: Some(3)) } // --- Loop accumulation --- @loop_insert () -> {int: int} = { let acc: {int: int} = {}; for i in 0..10 do { acc = acc.insert(i, i * 10); }; acc } @test_loop_insert tests @loop_insert () -> void = { let m = loop_insert(); assert_eq(actual: m.len(), expected: 10); assert_eq(actual: m.get(5), expected: Some(50)) } // --- Keys and values after COW --- @keys_after_cow () -> int = { let m = {"a": 10, "b": 20}.insert("c", 30); m.keys().len() } @test_keys_after_cow tests @keys_after_cow () -> void = { assert_eq(actual: keys_after_cow(), expected: 3) } // --- Empty map insert --- @empty_insert () -> ({str: int}, {str: int}) = { let empty: {str: int} = {}; let filled = empty.insert("first", 1); (empty, filled) } @test_empty_insert tests @empty_insert () -> void = { let (empty, filled) = empty_insert(); assert_eq(actual: empty.is_empty(), expected: true); assert_eq(actual: filled.len(), expected: 1) } ``` ## tests/spec/free_floating_test.ori — first-class test declarations (`tests` syntax) ```ori // Test that free-floating tests work // Spec: 13-testing.md § Free-Floating Tests // Design: 11-testing/02-test-syntax.md § Free-Floating Tests use std.testing { assert, assert_eq } // Free-floating test using explicit `tests _` syntax @test_arithmetic tests _ () -> void = { let a = 2 + 3; let b = 10 - 5; assert_eq(actual: a, expected: b) } // Another free-floating test using `tests _` @test_boolean_logic tests _ () -> void = { assert_eq(actual: true && true, expected: true); assert_eq(actual: true && false, expected: false); assert_eq(actual: false || true, expected: true) } // Free-floating test (migrated from legacy name-prefix convention) @test_string_ops tests _ () -> void = { assert_eq(actual: "hello" + " " + "world", expected: "hello world") } ``` --- Source of truth: https://raw.githubusercontent.com/upstat-io/ori-lang/master/docs/ori_lang/v2026/spec/