AHA! Lang 1.4.1–1.4.3: Complete Structs, 384 Tests

Three consecutive releases bring structs from literals to function parameters and return values — with 384 automated tests.

August 16, 2026

AHA! Lang 1.4.1–1.4.3: Complete Structs

Three consecutive releases — 1.4.1, 1.4.2, 1.4.3 — complete the first pillar of the Phase 2 roadmap: structs fully working at runtime. From literal codegen, to field mutation, and finally structs as function parameters and return values.

1.4.1 — Struct Codegen

struct Point { x, y } now produces real LLVM struct types:

struct Point { x, y }

let p = Point { x: 3, y: 4 };
let total = p.x + p.y;   // 7
  • Literals Point { x: 1, y: 2 } are built via LLVM insertvalue
  • Field access p.x reads via extractvalue
  • Typed fields: name: string, age: int are honored at runtime — string fields use {i8*, i64} layout, int fields use i64
  • Types are checked at compile time: assigning a string to an int field is an error
  • Field access preserves the declared type, so len(p.name) and p.first + p.last work

1.4.2 — Field Mutation

p.x = value becomes a true lvalue:

p.x = 9;             // load struct → insertvalue → store back
p.name = "baru";     // string fields can be mutated too
  • The parser now treats = as an infix operator — AssignmentExpression has a generic target, opening the door to array assignment arr[0] = x in the future
  • Fixed: Assign missing from the precedence table
  • 373 tests passing

1.4.3 — Struct as Parameter & Return Value

Functions now accept structs by value and return them:

fn sum(p) { p.x + p.y }
sum(Point { x: 40, y: 2 });      // 42 — literal passed directly as an argument

fn make(x, y) { Point { x: x, y: y } }
sum(make(20, 22));               // 42 — chained: return value flows straight in

fn shifted(p) { p.x = p.x + 100; p }
let q = shifted(p);              // mutation only touches the local copy
  • sum(make(20, 22)) — a struct return value flows directly into another function
  • String fields work inside functions too: len(p.name) + p.age
  • The pre-pass type inference now recognizes StructLiteral and FieldAccess, with a fixpoint so parameter types stabilize even when call sites appear later in the source

Quality: 417 Automated Tests

ReleaseTestsFeature
1.4.084Type system, strings, loops
1.4.1363Struct codegen
1.4.2373Field mutation
1.4.3384Struct param/return
1.4.4404Type annotations & inference
1.4.5417Generic functions

All verified via CI on the development branch — cargo check, cargo test --test-threads=1, cargo build --release — and the results are deterministic.

New in 1.4.5: Generic Functions

fn pick<T>(a: T, b: T) -> T — functions with generic type parameters, with the concrete type inferred from each call site:

fn id<T>(x: T) -> T { x }
fn pick<T>(a: T, b: T) -> T { if a > b { a } else { b } }
fn first<A, B>(a: A, b: B) -> A { a }

pick(3, 7);            // 7  — instantiated as pick_Int
len(id("hello"));      // 5  — instantiated as id_String

Each unique (name, concrete types) combination becomes a separate LLVM function at compile time — monomorphization with zero runtime cost. Return types can be annotated (-> T, -> int), and generic functions can call other generic functions.

Stabilize First, Then Move Step by Step

With F1 (structs), F2 (type annotations), and F3 (generics) stable, the roadmap continues to F4 — Module System & Package Manager (aha install). Resource lifetimes (F5) remain frozen until the foundation is stable — per the principle stabilize first, then move step by step.

Follow the development on the GitHub repo and check the compiler-verified Syntax Reference.