All notable changes to Luz are documented here.
Format: ## [version] - YYYY-MM-DD followed by categorized entries.
C codegen — correctness
- List literals (
[1, 2, 3]) now compile correctly —HirListwas unhandled and silently emitted0(#89) - List indexing (
list[n]) no longer crashes — integer indices are now converted to string keys vialuz_idx_key()before dict lookup (#90) - Nested function definitions inside functions are no longer silently dropped — a pre-pass hoists them to top-level with proper forward declarations (#91)
range()builtin now works — emits aLuzDict*with sequential integer keys (#92)append()andpop()builtins now work — addedluz_list_append/luz_list_popto the runtime (#93)sort(),type(),typeof()builtins no longer produce a TCC linker error (#94)- Method return values no longer lose their type — added
HirObjectCallandHirFieldLoadcases toinfer_type(), fixing string/float methods (#95) - Nested
attempt/rescueblocks no longer corrupt the rescue stack — removed the duplicateluz_pop_rescue()call from the rescue branch (#100) - Functions whose body returns a float or string
BinOpexpression are now forward-declared with the correct return type instead oflong long(#99)
Runtime (luz_rt.c)
luz_powi()now raises a structured fault on negative exponents instead of silently returning1(#97)dict_setno longer leaks the previous string heap allocation when overwriting an existing string entry (#98)
luz_idx_key(long long)— integer-to-string-key helper used by list indexing andrange()luz_list_sort(LuzDict*)— bubble sort over integer-valued list entriessort(),type(),typeof()handlers in C codegen
Zero-dependency native compiler
- New compilation pipeline: Luz source → HIR → C source → TCC → native binary — no LLVM, no clang required
- C code emitter (
ccodegen.cpp) that lowers HIR directly to self-contained C99 source files - Bundled TCC v0.9.27 (x86-64 Windows, ~100 KB) — users need no external toolchain to compile Luz programs
--emit-cCLI flag: print generated C source to stdout or write to file with-oLUZ_TCCenvironment variable to override the TCC binary pathLUZ_RTenvironment variable to override the runtime C file path- TCC is auto-discovered via
LUZ_HOME(set by the installer),LUZ_TCCenv var, compile-time bundled path, ortccin PATH — in that order listen(prompt)builtin: reads a line from stdin (equivalent to Pythoninput())to_int(s),to_float(s),to_bool(s)builtins: string-to-primitive conversions
C runtime (luz_rt.c)
- String operations:
luz_str_concat,luz_str_len,luz_str_eq,luz_str_contains - Conversion helpers:
luz_to_str_int,luz_to_str_float,luz_to_str_bool - Type conversions:
luz_to_int,luz_to_float,luz_to_bool - I/O:
luz_listen(prompted line input) - Dict/list:
luz_dict_new,luz_dict_set_*,luz_dict_get_*,luz_dict_len,luz_dict_contains,luz_dict_remove - Exception system:
luz_push_rescue_ptr,luz_pop_rescue,luz_alert_throw,luz_get_error— backed bysetjmp/longjmp - Math:
luz_powi(integer exponentiation)
Installer
- Windows installer now bundles TCC under
{app}\tcc\— no post-install setup needed LUZ_HOMEregistry variable set at install time soluzcfinds TCC and the runtime automatically
--emit-llvmis now marked legacy;--emit-cis the primary IR inspection flag- Release workflow copies
compiler/tools/tcc/intodist/tcc/before running Inno Setup
- 2.0 is a beta release: core language features (functions, classes, control flow, strings, dicts,
attempt/rescue) are fully supported through the new pipeline; some advanced builtins may not yet be wired to C codegen - The Python interpreter (
luz.exe) remains available for running.luzfiles interpreted;luzcis the native compiler
Compiler pipeline (interpreter → native executable)
--compile <file> [-o output]CLI flag: compiles a.luzfile to a native executable via LLVM IR → object file → linker--emit-ir <file>CLI flag: prints the generated LLVM IR to stdout--run <file>CLI flag: compiles to a temporary executable, runs it, then deletes the binary--emit-ast <file>CLI flag: prints the full AST (class names + fields) produced by the parser for debugging--emit-hir <file>CLI flag: prints the lowered HIR using dataclass reprs for debugging- HIR (High-level Intermediate Representation) lowering pass in
luz/hir.py— 27 node types that desugar the AST into a flat, compiler-friendly representation before code generation; desugaring includes:for→ while loop,for x in list→ index-based while,switch/case→ if/elif/else,match→ equality chain,x ?? y→ null check,a if cond else b→ ternary if, f-strings → string concatenations - LLVM IR code generator in
luz/codegen.py— lowers HIR to LLVM IR via llvmlite; all values are represented uniformly asluz_value_t {i32 tag, i32 pad, i64 data}matching the C runtime ABI - Native C runtime library in
luz/runtime/— implements the full Luz type system and 40+ builtin functions as C functions:luz_runtime.h(tagged union, SSO strings, dynamic arrays, open-addressing hash table, vtable objects, setjmp/longjmp exceptions),luz_runtime.c(ARC retain/release, all data structure operations, builtins), dynamic dispatch helpers inluz_rt_ops.c - Tail call optimization (TCO) in the compiler: tail-position calls to user functions are marked
musttail(self-recursive) ortail(cross-function); the LLVM TCO pass then eliminates the call frame, enabling stack-safe deep recursion in compiled mode - Indirect calls and function-pointer support in compiled mode:
_gen_HirExprCallnow stores function pointers inluz_value_t{TAG_FUNCTION}and performs real indirect calls viainttoptr; functions stored in variables, passed as arguments, or returned from other functions work correctly under--compile
Type checker
- Generic collection types
list[T]anddict[K, V]are now enforced by the type checker — element types are validated on assignment and on function call arguments/return values - Definite assignment analysis: raises
UninitializedFaultfor variables that may be read before being written on all control flow paths;ifwithoutelsedoes not guarantee assignment,if/elsepropagates guaranteed names to the parent scope,while/forloop bodies are not guaranteed
HIR optimizer
- Constant folding in the HIR lowering pass: literal binary expressions (
3 + 4,"hello" + " world",not false) and unary expressions (-5) are evaluated at compile time inlower_BinOpNode/lower_UnaryOpNodeand replaced with a singleHirLiteral, eliminating the runtime call entirely
Standard library
luz-func— functional programming utilities (import "func"):higher.luz:map,filter,reduce,each,flat_map,zip_with,take_while,drop_while,count_if,find_first,group_bycompose.luz:identity,constant,compose,compose3,pipe,flip,negate,all_of,any_ofpartial.luz:partial,partial2,once,memoize
inandnot inoperators on dicts now work correctly — previously only lists and strings were supported- Unary
+operator is now accepted (it is a no-op, like in most languages) instanceof()now works correctly with struct instancesbreak,continue, andreturnused outside their valid scope now raise the proper Luz error instead of leaking a Python exceptionsort()on a mixed-type list (e.g.[3, "a", 1]) now raisesTypeViolationFaultinstead of leaking an internal PythonTypeError- Type checker:
string * intandint * stringare now accepted as valid string repetition — previously the type checker rejected them even though the interpreter handled them correctly pop()error message is now more descriptive when called on an empty listpath_ext()inluz-systemnow returns only the file extension instead of the full basename- Windows x64 ABI mismatch in
--compile: added pointer-wrapped_pwshims for all runtime functions that pass or returnluz_value_tby value, so LLVM-generated code and MinGW-compiled C agree on the struct calling convention;lower_BinOpNodeinhir.pywas also fixed where a missingopfield caused compiled arithmetic to always output null - C runtime: replaced GCC-specific
__attribute__((noreturn))with a portableLUZ_NORETURNmacro that falls back gracefully on MSVC and other compilers
- Lexer: identifier and keyword matching now uses
frozensetfor O(1) lookups; string token accumulation uses list+join instead of repeated string concatenation — measurably faster on large source files - Compiler: LLVM middle-end optimization pipeline now runs before native object emission — applies SROA/mem2reg (promotes alloca/store/load triples to SSA registers), instruction combining, CFG simplification, dead store elimination, aggressive DCE, constant merging, and global optimization; at
-O2also applies tail call elimination, jump threading, and memcpy optimization
- Nullable types:
T?syntax — a variable declared asint?accepts bothintvalues andnull; a plainintrejectsnullat runtime and at the type-checker level - Generic collection types:
list[T]anddict[K, V]— type parameters are enforced on assignment and on function call arguments/return values - Fixed-size numeric types:
int8,int16,int32,int64,uint8,uint16,uint32,uint64,float32,float64— backed by Pythonint/floatat runtime with range/overflow checks structkeyword for typed, value-type data containers with optional default field valuesconstkeyword for immutable bindings — reassignment raisesInvalidUsageFault- Compile-time type checker pass — runs after parsing and before execution; collects all type errors rather than stopping at the first one
- Unused variable, import, and parameter detector (Go-style) — names prefixed with
_are exempt string.len()dot method, consistent withlist.len()anddict.len()list.sort()andlist.reverse()dot methods- Type checker now infers return types through function calls and propagates them through arithmetic (
int + float → float,int / int → float, etc.) - Type checker now tracks class attribute types inferred from
initbody and returns the correct type forinstance.attraccess
clamp()now raisesArgumentFaultwhenlow > highinstead of silently returning a wrong valueinsert()now raisesIndexFaultfor out-of-bounds indices- C lexer bridge:
selftokens were emitted without a value (None); now correctly carry'self'to match the Python lexer
- Scope chain:
assign()previously walked the scope chain twice (once to check existence vialookup(), once to update); replaced with a single_find_scope()traversal (~31% faster on variable-heavy programs) - Type checking: parsed generic type strings (
list[int],dict[string, int], …) are now cached inInterpreter._TYPE_PARSE_CACHEso the character-by-character bracket parse runs only once per unique type string (~1.5x faster on the type-check hot path)
- Slice syntax for lists and strings:
list[start:end],list[start:end:step],string[start:end] - Typed variable declarations:
x: int = 5— type is enforced at assignment - Type annotations on function parameters and return values now respect class inheritance — a subclass instance satisfies a parent type annotation
insert(list, index, value)builtin — inserts an element at a position, shifting elements right- Dict dot method syntax:
dict.keys(),dict.values(),dict.len(),dict.contains(key),dict.remove(key) luz-typesstandard library — type predicates (is_int,is_float,is_number, etc.), safe casting (safe_int,safe_float,safe_str,safe_bool), and schema validation (validate)
exp(x)was callingmath.log(x)internally — now correctly callsmath.exp(x)swap(),append(),contains(),join(),remove()andinsert()dot methods now raiseArityFaulton missing arguments instead of crashing with a PythonIndexError0 ** negativenow raisesZeroDivisionFaultinstead of leaking a raw PythonZeroDivisionErrorstring // intandround(x, non-int)now raise proper Luz faults instead of leaking a raw PythonValueErrorint in stringnow raisesTypeClashFaultinstead of leaking a raw PythonTypeErrorsplit(s, "")now raisesArgumentFaultinstead of leaking a raw PythonValueErrorfinallyblock no longer silently discards an exception that was already pending from therescueblock — the original error is preservedfrom "x" import a, bis now atomic — if any name doesn't exist, nothing is imported into scope
- Bound methods: retrieving a method from an instance (
m = obj.method) now returns a bound method that carriesselfautomatically — callingm()no longer requires passing the instance manually - String dot method syntax:
str.uppercase(),str.lowercase(),str.trim(),str.swap(old, new),str.split(sep) - List dot method syntax:
list.append(x),list.pop(),list.len(),list.contains(x),list.join(sep) - Versioning policy documentation (
docs/versioning.md) - Automated release notes: tagging
vX.Y.Znow extracts the matching changelog entry and sets it as the GitHub release body
self.attr[i]now correctly indexes into instance attributes — previously the index was silently ignored and the full attribute was returned- Lists, dicts, booleans, and
nullnow print in Luz syntax ("hello",true,null) instead of Python syntax ('hello',True,None)
- Pylint to CI pipeline with a minimum score of 9.0/10
.pylintrcconfiguration silencing false positives from intentional design decisions- Lint instructions to
CONTRIBUTING.md
from "module" import namesyntax for selective importsimport "module" as aliassyntax for aliased importsfinallyblock inattempt / rescuerescuenow accepts an optional error variable (can be omitted)- Unicode escape sequences (
\uXXXX,\UXXXXXXXX) in string literals - Scientific notation support for numeric literals (
1.5e10,3e-4)
forloop now validates step direction (errors if step sign contradicts range direction)- Recursion error messages now report the function name
- Block scope for
ifandwhile— variables declared inside do not leak out - Default function arguments now evaluate in caller scope, not closure scope
- CI test matrix across Python 3.10, 3.11, and 3.12
- 72-test pytest suite organized in 9 classes
typeofmodule resolution no longer errors on built-in type names- Scope leak: assigning to a variable no longer creates it in the wrong scope
- Circular inheritance no longer causes infinite recursion (raises
InheritanceFault) - Failed imports are properly deregistered so re-importing works correctly
len()no longer uses a bareexceptclause
switch / casestatementmatchexpression with multiple value patterns per case- Compound assignment operators:
+=,-=,*=,/= - Negative indexing for lists and strings (
list[-1]) - Destructuring assignment:
x, y = func()
- Multiple return values from functions
- Variadic arguments (
...args) - Named arguments at call sites
- Lambda expressions:
fn(x) => x * 2andfn(x) { body }
- Object-oriented programming:
class,extends, method overriding,super attempt / rescueerror handling andalertto raise errors- Module system:
import "path" - Ray package manager