Changelog
All notable changes to AHA! Lang
Changelog
All notable changes to AHA! Lang are documented in this file. You can always build the latest from source — see Getting Started.
1.4.5 — 2026-08-16
Added
- Generic functions (F3):
fn pick<T>(a: T, b: T) -> T { if a > b { a } else { b } }— functions with generic type parameters. - Monomorphization via LLVM: Each unique combination of (generic name, concrete type) becomes a separate LLVM function (
pick_Int,pick_String, ...), compiled lazily at the first call site and cached. Zero runtime cost — generics resolve entirely at compile time. - Multiple type params:
fn first<A, B>(a: A, b: B) -> A— concrete bindings are inferred from call-site arguments. - Return type annotation:
-> Tor-> int— generic or concrete return types. - Nested monomorphization: Generic functions can call other generic functions (
fn twice<U>(x: U) -> U { id(x) }). - Test Suite: 417 tests passing (was 404; +13 generics tests: identity int/string/bool/struct, pick, two type params, nested calls, IR shape).
Changed
predeclare_functionsstores generic functions ingeneric_defs(no direct pre-declaration); bodies compile during monomorphization.compile_functionskips generic function bodies at top level (only instantiated on call).- The fixpoint loop skips generic functions (return types only exist per instantiation).
Security
- Generics add no unsafe paths: all types are still verified at compile time; monomorphization is pure per-type codegen duplication.
1.4.4 — 2026-08-16
Added
- Type annotations (F2):
let x: int = 5— variable declarations can carry an explicit type annotation (int,string,bool, or a struct name). The value is type-checked at compile time:let x: int = "hi"→ compile errorType mismatch: variable 'x' annotated as 'int' but value has type 'String'let p: Point = Other { ... }→ compile error when the struct names differ
- Type inference (F2): Variables without an annotation get their type inferred from the expression (literal, function call, if-expression).
- Function return type inference:
fn greet() { "hello" }has return type String — callers can dolet s = greet(); len(s). - If-expression with string branches:
fn pick(a) { if a > 0 { "pos" } else { "neg" } }now returns a real string (the phi node uses the branch types instead of always i64). - Test Suite: 404 tests passing (was 384; +20 type inference/annotation tests).
Changed
infer_expr_type&infer_expr_type_with_scopesupport annotations and function return type inference.- Unknown type hints (
let x: unknown = 7) are lenient — they fall back to Int, consistent with struct field hints.
Security
- Type annotations are verified at compile time: mismatching an annotation with the value's type produces a compile-time error, not undefined behavior.
1.4.3 — 2026-08-16
Added
- Syntax Reference page: A complete, compiler-verified reference of all AHA! syntax — 13 keywords, identifiers, literals, types, operators with precedence table, statements, expressions, control flow, comments, builtins, and an EBNF grammar summary. Available at /en/syntax (EN) and /id/syntax (ID).
- Struct as function parameter (F1): Structs can now be passed by value into functions. Parameters are allocated with the correct LLVM struct type, so field access inside the function works normally.
- Struct as function return value (F1): Functions can return struct literals. The caller stores the result in a
letbinding and reads its fields. - Struct literal passed directly as an argument:
sum(Point { x: 1, y: 2 })— no intermediate variable needed. - Chained struct calls:
sum(make(20, 22))— a struct return value feeds directly into another function. unify_with()on AhaType: Merges parameter types from multiple call sites (String and struct names override the Int default).- Test Suite: 384 tests passing (was 373; +11 struct param/return tests).
Changed
infer_expr_type&infer_expr_type_with_scopenow recognizeStructLiteralandFieldAccessfor accurate pre-pass type inference.scan_call_sitestracks struct variable bindings (struct_var_types) so type inference can resolve struct variables passed as function arguments.- Language Tour: Fixed
forloop syntax fromfor i 0..10tofor i in 0..10— theinkeyword is required by the parser. Also added "Syntax Reference" to the docs navigation.
1.4.2 — 2026-08-16
Added
- Field Mutation (F1):
p.x = value— struct field assignment at runtime.p.xis now an lvalue: load the struct,insertvaluethe new field, store back. Type-checked against the field's declared type (stringfields reject int values and vice versa). - Generic Assignment Target: The parser now treats
=as an infix operator, so bothx = 5(identifier) andp.x = 5(field access) parse toAssignmentExpressionwith a generictargetexpression. - Test Suite: 373 tests passing (was 363; +10 field-mutation tests, including mutation inside loops and string field reassignment).
Changed
AssignmentExpression.name: Identifier→AssignmentExpression.target: Box<Expression>— enables future lvalue forms (e.g. array element assignmentarr[0] = x).
Fixed
TokenType::Assignwas missing fromprecedence(), so=fell through toparse_prefixas an unexpected token. AddedPrecedence::Assign.
Security
- Assigning a value of the wrong type to a typed field produces a compile-time error.
1.4.1 — 2026-08-16
Added
- Struct Codegen (Roadmap Phase 2 #1):
struct Point { x, y }definitions now produce real LLVM struct types. LiteralsPoint { x: 1, y: 2 }build the aggregate viainsertvalue; field accessp.xreads viaextractvalue. - Typed Struct Fields: Field type hints (
name: string, age: int) are honored at runtime —stringfields use{i8*, i64}layout,intfields usei64. Literals are type-checked against declarations. Field access preserves the declared type, solen(p.name),p.name == "...", andp.first + p.lastwork correctly. - Struct Literal Syntax:
TypeName { field: value, ... }— gated by a struct-name registry so ordinary block conditionsif x { ... }are unaffected. - PRD (Product Requirements Document):
PRD.md— defines AHA!'s vision from web to aerospace, roadmap priorities, and project governance. - Test Suite (struct): 28 backend tests covering JIT semantics, IR shape, typed fields, and error paths.
Changed
- Branch workflow: All development now happens on the
developmentbranch;mainonly receives PRs with green CI. - PRD replaces ad-hoc planning: All future features must originate from the roadmap in
PRD.md. - F5 (Resource lifetimes) frozen: Ownership/lifetimes will not be touched until F1–F4 (struct finalization, type inference, generics, module system) are stable.
Security
- Struct field types are checked at compile time: assigning a string literal to an
intfield (or vice versa) produces a compile-time error instead of undefined behavior.
1.4.0 — 2026-05-17
Added
- Type System (
src/types.rs):AhaTypeenum (Int,Bool,String,Void,Array,Function) with compile-time type checking viacheck_binary_op()andcheck_prefix_op() - TypedValue: all codegen expression methods now return a
TypedValuepairing an LLVM value with itsAhaType - String Type: strings are now an LLVM struct
{i8*, i64}(pointer + length), replacing the unsafe pointer-to-int cast - String Concatenation:
"hello" + " world"allocates a new buffer viamalloc, copies viamemcpy, null-terminates - String Comparison:
==and!=on strings usestrcmpfrom the C standard library len()builtin: returns a string's length in O(1) by reading the struct's length field- C Runtime Linkage: external declarations for
malloc,memcpy,strlen,strcmp - Block Comments:
/* ... */multi-line comments - String Escape Sequences:
\n,\t,\\,\",\r,\0 - Identifier Improvements: digits after the first character (
my_var2), underscore prefix (_private) - Test Suite: 84 tests across 4 modules — lexer, parser, type system, and end-to-end JIT
Changed
Token.r#typerenamed toToken.kind— follows Rust convention, eliminates raw identifier syntax- Variable scope: flat map → stack of scopes with type tracking
compile_expression()returnsTypedValuethroughout all expression handlers- All output messages and source comments standardized to English
- README roadmap updated to accurately reflect implementation status
Fixed
!=operator produced"=="instead of"!="(copy-paste error in the lexer)ifconditions not converted fromi64toi1beforebuild_conditional_branch- Phi nodes referenced original basic blocks instead of actual end blocks
- Last expression in program body was compiled twice
- Functions emitted both implicit and explicit return, causing LLVM "multiple terminators" errors
- Function compilation could leak scope/builder state on error
- Parser returned
Expression::Identifier("ERROR")on parse failure, propagating invalid AST to codegen
Security
- Type mismatches (
"hello" + 5) now produce compile-time errors instead of undefined runtime behavior - All
.unwrap()calls replaced with.expect("descriptive context")for debuggable panics
1.3.0 — Previous Release
- Initial compiler with lexer, parser, codegen
- Integer, boolean, string (as pointer hack) types
- If/else, while, for loops
- Functions with parameters
- Basic stdlib: print, print_str, abs, min, max