Gengoscript Changelog
Unreleased
Language (unreleased)
??null-coalescing operator —a ?? breturnsaif it is notnull, otherwise evaluates and returnsb. Short-circuits (right-hand side is skipped when the left-hand side is non-null). Right-associative:a ?? b ?? cisa ?? (b ?? c).- Generic struct and variant types —
type Stack[T] struct { items []T }andtype Result[T, E] variant { ok(value T), err(e E) }. Instantiate by supplying concrete type arguments:Stack[int]{ items: [] },Result[int, string].ok{ value: 42 }. Type parameters are substituted at compile time; each instantiation is a distinct concrete type. Nested generic fields (e.g.Stack[T]insideWrapper[T]) resolve correctly when the outer type is instantiated. - Generic functions —
func map_array[T, U](xs []T, f func(T) U) []U. Type arguments may be omitted and are inferred from the call context:map_array(nums, fn). Explicit type arguments are also accepted and their count is validated:map_array[int, string](nums, fn). Type parameters are erased at runtime (treated asany). - Generic constraints — Type parameters may carry built-in constraints using
:syntax:func sort[T: ordered](xs []T). Constraints are enforced at explicit call sites. Three built-in constraints are supported:numeric(int, float, decimal, rune, and named subtypes),ordered(numeric + string and named string subtypes), andcomparable(any type). - Generic type aliases —
type IntStack Stack[int]declares a named alias for a concrete generic instantiation. The alias is fully transparent: it may be used anywhere the underlying type may be used, including struct literals, function parameter types, and.typecomparisons. - Named error types —
type NotFound errordeclares a named error type. Named errors are ordinaryerrorvalues and can be distinguished by.type:e.type == NotFound,switch e.type { case NotFound { ... } }. whenguards on switch cases — Both value and variant cases may carry awhencondition. The guard sees theasbinding. A failed guard falls through to the next case:case .ok as v when v > 0 { ... }.- Exhaustiveness checking for variant switch — A variant
switchinside a function is now checked at compile time. Every arm must be covered by at least one unguarded case, or the switch must have adefault. A missing arm is aNonExhaustiveSwitchcompile error. - Default parameter values — Trailing function parameters may declare a literal default (
func f(a int, b int = 1) int). Callers may omit any suffix of defaulted parameters. - Hex, binary, and octal literals —
0xFF,0b1010_1111,0o755. Digit separators (_) work in all bases. Valid in expressions, range bounds, and default values. - Enum representation values —
type Status enum { pending = 0, active = 1, done = 2 }. Unspecified members auto-increment..inton a value returns its integer representation.T.from_int(n)does the reverse lookup, returning?T. - Enum value properties —
.nameand.intare now first-class field accesses on any enum value. - Struct zero-initialisation —
var p Pointnow creates a zero-value struct instance (fields set to0,false,""by their type). Previously it producednull. - Enum zero-initialisation —
var s Statusnow initialises to the first declared member. Previously it panicked. - Range bounds with any numeric base —
type Port int range 1..0xFFFFand similar are now valid. - Named type bounds —
T.firstandT.laston ranged named types return the boundary as a named-type value. clamp— a third named-type range mode —type Percent int clamp 0..100saturates an out-of-bounds value to the nearest bound instead of raising a range error (range) or wrapping (cycle). Works onint/float/decimal/runebases, is inherited by subtypes, and composes withpredicate(clamping runs first; the predicate then checks the clamped result).defaultis still validated strictly against the range regardless of mode.- Limited operator overloading via reserved dunder methods — a struct (or non-conflicting-base named) type may declare
__add__/__sub__/__mul__/__div__/__rem__/__neg__/__eq__/__compare__as ordinary methods;a + bdesugars toa.__add__(b)when the compiler can resolvea's static type at compile time, and falls back to a runtime lookup inside type-erased generic function bodies.__compare__backs all four ordering operators at once (Kotlin'scompareTotrick);!=is derived from__eq__. No asymmetric operand form — the parameter type must be exactly the receiver's own type. A named type may only declare a dunder for an operator its base doesn't already implement (declaring__add__on anint-based type is a compile error). A type declaring__compare__/__eq__also satisfies theordered/comparablegeneric constraints.
Standard Library (unreleased)
cap:env— New capability module for process environment access.env.get(name)returnsstring|null;env.list()returns[string]string. Requires the host to enable theenvcapability; a script importingcap:envwithout host permission fails at compile time.std.Arg— Built-in variant covering all primitive scalar types (Int,Float,Decimal,Rune,Bool,Str,Err). Use it to write type-safe heterogeneous variadic functions without exposingany.
Tooling (unreleased)
gengo --test --profile— reports eachtestblock's instruction count and peak heap bytes/stack depth/live object count, plus a final peak-across-all-blocks summary line, so integrators can sizeengine_init_with_config's resource ceilings from measured workload data instead of guessing. Does not affect pass/fail behavior or the exit code; does cost real speed (forces per-instruction accounting on for the run), so it's a diagnostic flag, not something to leave on by default.gengo --emit-gbc path/ running a.gbcdirectly — an early GBC (Gengo Bytecode Cache) implementation:--emit-gbccompiles a script and writes a.gbcartifact instead of running it; a.gbcfile passed as the script argument (recognized by magic bytes) loads and runs directly, skipping parsing and compilation. Covers plain functions (with real parameter/return types, so interface conformance checks keep working on a loaded function),std/native calls, and struct, named-type,variant, andinterfacedeclarations (including range/cycle/clamp-constrained named types, a named type'sdefaultvalue andscale, a module/type-scopepredicate, and variants with shared fields and record/single-payload/no-payload arms); enums, a predicate declared inside a function body, and closures with real captures stored as constants aren't supported yet —--emit-gbcreports these clearly rather than producing a broken artifact. Seedev-docs/design/gbc-spec.mdand GitHub issue #5 for the full format and remaining scope; the CLI does not yet check a.gbc's recorded source hash against the current source before running it, so regenerate one by hand when its source changes.
Fixes (unreleased)
- Named-return of a boxed named type — a function with a named return of a named type over a non-scalar base (e.g.
type Tag string, used asfunc f() (result Tag)) panicked withNotAFunctionwhen returning a value explicitly (return Tag(s)). Named returns of scalar/enum-based named types (type Meters int) were unaffected. - Struct and enum variable declarations following a
stdimport no longer trigger a spurious "unknown field in std" compile error. - Arity mismatch errors now show
expected 1-2 argument(s)when a function has defaults, and no longer include a stray leading comma in the function signature. - Unknown
std.*namespace fields now suggest the closest matching name in the error message. - Named-type range and predicate validation at call boundaries — a dynamically-typed value (from
std.json.parse, a host embedding call, or any other erased/anysource) arriving at a function parameter or return value declared as a named range or predicate type is now actually validated against that type's constraint. Previously the validation error was silently discarded and the raw, unchecked value was passed through —Port(99999)panicked correctly when constructed directly, but a script or host could smuggle99999past aPort int range 1..65535parameter without it ever panicking.
v0.5.0 — 2026-06-15
Language (v0.5.0)
- Boolean-only conditions —
if,not,and,or, and template{{if}}now require actualboolvalues. Non-boolean values (non-zero integers, non-null strings, etc.) that were previously treated as truthy/falsy now produce a runtimeTypeError. Usestd.conv.to_boolfor explicit conversion. - Type names cannot be shadowed — No binding form may use a type name: variables, functions, parameters, loop variables, and named returns all reject primitive type names and every declared type.
- Subtypes of any scalar named type —
subtype Child Parentnow accepts any scalar named parent (bool,string,decimaljoinint/float/rune).range/cycleconstraints still require a numeric parent. - Named string concatenation —
+on two values of the same named string type preserves the named type. - Predicate custom messages — A predicate may declare a custom failure message:
predicate func(x) { ... } message "must be positive". - Predicate inheritance — Subtypes inherit the parent’s predicate. A subtype with its own predicate must satisfy both.
- Bare type parameters in interface specs — Interface method parameters can be written as bare types, Go-style:
add(float) float. - Enum subtype validation —
subtype Bogus Days { tuesday }with a member the parent does not have is now a compile error instead of compiling silently. - Unicode identifiers — Identifiers may contain Unicode letters and decimal digits, following the same rules as Go.
Standard Library (v0.5.0)
- std.regexp — Backtracking NFA regexp engine with
.match,.find,.find_all,.replace,.split, and.compile. - std.string.builder — Mutable string builder with
.write,.str, and.reset. - std.array —
filter,map,reduce,slice,zip,flat,find,find_index,all,any,chunk. - std.sort —
asc,desc,by. - std.hex —
encode,decode. - std.base64 —
encode,decode,url_encode,url_decode. - std.time —
parse_duration,iso_week,add_date,since,until. - std.string —
count,fields,pad_left,pad_right,equal_fold,contains_any,trim_left,trim_right,trim_prefix,trim_suffix,split_n.
Runtime & Embedding
- Runtime error messages — Type and range errors now explain what went wrong and how to fix it (e.g.
cannot mix Age and int; wrap the int with Age(...)). - Host module failure modes — Host module calls that return
unsupported,denied,bad_args, orfailednow raise distinct runtime errors (HostNativeUnsupported,PermissionDenied,HostNativeBadArgs,HostNativeFailed). - REPL type persistence —
type,subtype,struct,interface, andvariantdeclarations now persist across REPL lines. - Instruction budget —
max_opsinapi.Configenforces a hard limit on VM instructions per run.
Fixes (v0.5.0)
- Strict int/float comparison — Ordering comparisons now follow the same strictness as arithmetic:
1.5 > 1is aTypeError, matching1.5 + 1. - Nominal type strictness — Named-type values no longer mix with bare base-type values in arithmetic, comparison, or compound assignment.
- Expression recursion limit — Deeply nested expressions now fail compilation with
ExpressionTooDeep(limit 256) instead of risking a host stack overflow. - Module load failure — A failed module is now marked
.failedinstead of staying in.loading, so subsequent imports report the original error instead of a misleadingImportCycle. - Host module exports — Accessing a non-existent field on a host module now produces a compile-time error instead of surfacing at call time.
- String pool exhaustion — When the 128KB string pool overflows, the lexer reports
"string pool exhausted (max 128KB)"instead of"unterminated string".
For the full internal development log, see the root CHANGELOG.md.