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: -> T or -> 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_functions stores generic functions in generic_defs (no direct pre-declaration); bodies compile during monomorphization.
  • compile_function skips 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 error Type 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 do let 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_scope support 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 let binding 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_scope now recognize StructLiteral and FieldAccess for accurate pre-pass type inference.
  • scan_call_sites tracks struct variable bindings (struct_var_types) so type inference can resolve struct variables passed as function arguments.
  • Language Tour: Fixed for loop syntax from for i 0..10 to for i in 0..10 — the in keyword 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.x is now an lvalue: load the struct, insertvalue the new field, store back. Type-checked against the field's declared type (string fields reject int values and vice versa).
  • Generic Assignment Target: The parser now treats = as an infix operator, so both x = 5 (identifier) and p.x = 5 (field access) parse to AssignmentExpression with a generic target expression.
  • Test Suite: 373 tests passing (was 363; +10 field-mutation tests, including mutation inside loops and string field reassignment).

Changed

  • AssignmentExpression.name: IdentifierAssignmentExpression.target: Box<Expression> — enables future lvalue forms (e.g. array element assignment arr[0] = x).

Fixed

  • TokenType::Assign was missing from precedence(), so = fell through to parse_prefix as an unexpected token. Added Precedence::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. Literals Point { x: 1, y: 2 } build the aggregate via insertvalue; field access p.x reads via extractvalue.
  • Typed Struct Fields: Field type hints (name: string, age: int) are honored at runtime — string fields use {i8*, i64} layout, int fields use i64. Literals are type-checked against declarations. Field access preserves the declared type, so len(p.name), p.name == "...", and p.first + p.last work correctly.
  • Struct Literal Syntax: TypeName { field: value, ... } — gated by a struct-name registry so ordinary block conditions if 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 development branch; main only 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 int field (or vice versa) produces a compile-time error instead of undefined behavior.

1.4.0 — 2026-05-17

Added

  • Type System (src/types.rs): AhaType enum (Int, Bool, String, Void, Array, Function) with compile-time type checking via check_binary_op() and check_prefix_op()
  • TypedValue: all codegen expression methods now return a TypedValue pairing an LLVM value with its AhaType
  • 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 via malloc, copies via memcpy, null-terminates
  • String Comparison: == and != on strings use strcmp from 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#type renamed to Token.kind — follows Rust convention, eliminates raw identifier syntax
  • Variable scope: flat map → stack of scopes with type tracking
  • compile_expression() returns TypedValue throughout 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)
  • if conditions not converted from i64 to i1 before build_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