14 Compile-Time (#!) and Type Reflection
Edva uses a unified compile-time evaluation and structural reflection model introduced by #! and the Type primitive. Type values, field cursors (f), and variant cursors (v) exist strictly at compile time and are erased before LLVM code generation with zero runtime footprint (E2018 / E3129).
14.1 Compile-time evaluation (#!) forms
| Construct | Syntax | Semantics | Diagnostic |
|---|---|---|---|
| Compile-time expression | val = #! expr |
Evaluates pure expression expr during compilation and embeds the constant result |
E3129: runtime-only op in #! |
Explicit Type parameter |
f: #! T: Type => Intf = #! T => #size T |
Specializes f per concrete Type passed at the call site (f(Int)) |
E2018: Type used as runtime value |
Inferred Type parameter |
f: #! T: Type, T => Stringf = #! T, x => T.name |
Deduces T automatically from the runtime argument x (f(42) \(\Rightarrow\) T = Int) |
— |
| Compile-time constant param | f: #! N: Int, Int => Int |
Specializes f on compile-time constant N |
— |
| Type generator | Pair: #! T: Type => (a: T, b: T)#type IntPair Pair(Int) |
Compile-time function returning a composite Type, registered via #type |
— |
#use io
Pair: #! T: Type => (first: T, second: T)
#type IntPair Pair(Int)
fact: Int => Int
fact = n => n !> 1 | 1 | n * fact(n - 1)
f5 = #! fact(5)
p = IntPair(10, f5)
io::out $ (p.first, p.second) // (10, 120)14.2 Type properties and compile-time intrinsics
Given T: Type, the compiler resolves the following properties as compile-time constants:
| Expression | Result Type | Specification |
|---|---|---|
#size T |
Int |
Target ABI byte size of T |
#align T |
Int |
Target ABI byte alignment of T |
T.name |
String |
Declared #type alias name (if nominal) or canonical type name ("Int", "String", etc.) |
T.zero |
T |
Default/zero-initialized value of T (respects field: Type = default on records) |
?T |
Int |
Field count for records, lane count N for (E * N), variant count for enums, 0 for () |
T.(i) |
String |
Variant name of the i-th enum variant (0 <= i < ?T; i must be compile-time constant, E3170) |
#use io
#type ServerCfg (host: String = "127.0.0.1", port: Int = 8080, tls: <>)
#type Mode < Fast, Safe >
cfg = ServerCfg.zero
io::out $ (ServerCfg.name, ?ServerCfg, cfg.host, cfg.port, Mode.(0), #size Int, #align Int)14.3 Structural type-shape guards (T [...])
When the scrutinee of a choice is T: Type, the compiler resolves the matching branch at compile time, prunes all untaken branches before type-checking/codegen, and binds inner component types in the taken branch scope:
| Guard Pattern | Matched Type Shape | Bound Type Variables in Branch Scope |
|---|---|---|
[Int, i32] |
Exact type equality (comma OR-list) | — |
[type::is_numeric] |
Compile-time Type => <> predicate (#use type) |
— |
[()] |
Unit (()) |
— |
[<>] or [<|>] |
Flag (<>) |
— |
[<P|>] |
RamPN option (< P | >) |
P: positive payload Type |
[<|N>] |
RamNP fallible (< | N >) |
N: negative payload Type |
[<P | N>] |
RamPP result (< P | N >) |
P: positive Type, N: negative Type |
[E{}] |
Growable array (E{}) |
E: element Type |
[E[]] |
Slice (E[]) |
E: element Type |
[(E * ..)] |
Any homogeneous tuple ((E * N)) |
E: element Type (?T gives length N) |
[(E * 4)] |
Homogeneous tuple of exact length 4 |
E: element Type |
[K ^ V] / [String ^ V] |
Hash map (K ^ V) |
K: key Type, V: value Type |
[(..)] |
Any record type | — (unroll fields with T @ f =>) |
[<..>] |
Any nominal enum type | — (unroll variants with T @ v =>) |
14.4 Compile-time record field (T @ f =>) and enum variant (T @ v =>) unrolling
Inside a #! generic function, iterating over a record Type ([(..)]) or enum Type ([<..>]) with @ unrolls the loop body at compile time across 0..?T:
| Cursor Expression | Record Field Cursor (T @ f =>) |
Enum Variant Cursor (T @ v =>) |
|---|---|---|
cursor.idx |
Int: 0-based field index |
Int: 0-based variant tag index |
cursor.name |
String: field name (or "0", "1" for tuples) |
String: declared variant label ("Red", etc.) |
cursor.type |
Type: static type of the field (FT = f.type) |
Type: payload type, or () for unit variants |
f.default |
< f.type | >: <+ default_val +> if declared, else <--> |
— |
val.(cursor) |
Reads field f from runtime record val (type f.type) |
Returns < v.type | > (<+ payload +> / <++> if active tag is v.idx, else <-->) |
val'.(f) = expr |
Writes expr to field f of val' (tracks definite init) |
— |
T(v) / T(v, p) |
— | Constructs runtime enum T for variant v |
#use io
#use type
#type Point (x: Int = 1, y: Int = 2)
#type Shape < Dot, Circle(Int), Box(Int) >
inspect_record: #! T: Type, T => String
inspect_record = #! T, rec =>
T
[(..)]
b = {64}
b += T.name + "("
T @ f =>
f.idx > 0 | b += ","
b += f.name + "=" + "${rec.(f)}"
b += ")"
+b
| ""
enum_tag_name: #! T: Type, T => String
enum_tag_name = #! T, val =>
T
[<..>]
b = {32}
T @ v =>
val.(v) | p =>
b += v.name
+b
| ""
p = Point(10, 20)
s = Shape(Circle, 5)
io::out $ (inspect_record(p), enum_tag_name(s))