Skip to main content
Version: 0.8.0

Changelog

v0.8.0

Sized numeric types, Char, List<T>, fixed-size arrays, turbofish, and fat-pointer &mut. Shipped from sprint/19.

New language features:

  • Sized numeric typesi8, i16, i32, i64, u8, u16, u32, u64, f32, f64 (RFC-0007, METEL-124). Sized literal suffixes: 42i32, 3.14f32, 255u8. All casts between sized types are explicit (as). Array indices must be u64.
  • Polymorphic numeric literals — unsuffixed integer and float literals unify with whatever numeric type the context demands (let annotation, function parameter, struct field, return type, or the other operand in a binary expression). Without context they default to i64 / f64. mut reassignment (m = 99 where m: i32) also propagates the declared type to the literal. Negative minimum literals (-128i8, -32768i16, -2147483648i32) are accepted at the lexer level.
  • Cross-sized numeric From impls — all 90 pairwise casts among the 10 numeric types are supported via as (i8 as u32, f32 as i64, etc.). Previously only i64 ↔ f64 was supported.
  • Char type — Unicode scalar value; single-quoted literals ('a', '\u{1F600}'); .to_u32() and Char::from_u32(n) conversions; implements Display (RFC-0007)
  • List<T> — standard growable-sequence type in std::core; replaces ad-hoc array_push usage; methods: new, from, push, pop, len, get, as_slice (RFC-0054)
  • Fixed-size array type [T; N] — compile-time-known length; repeat construction [v; N]; coerces to T[]; .len() method; array patterns on [T; N] (RFC-0053, METEL-135)
  • Turbofish — explicit type arguments at call sites: f::<T>(args), zip::<A, B>(as, bs) (METEL-124)
  • &mut for lvalue paths&mut obj.field, &mut arr[i], and chains thereof produce a *mut T that writes back to the original storage location (RFC-0045, METEL-134)

Bug fixes:

  • Generic functions with multiple independent type parameters (e.g. fold_left<T, A>) no longer have their type parameters collapsed when a module-level constraint solve follows a single-parameter generic function (METEL-137)
  • Same-tier glob import conflicts (import a::* and import b::* both exporting the same name) no longer raise an error at import resolution; the error fires at the first use site of the ambiguous name (METEL-98)
  • &mut x on a non-let mut binding is now a type error (T0006); previously accepted silently, allowing immutable bindings to be mutated through a pointer (METEL-118)
  • Field assignment (p.field = v) on a non-let mut binding is now a type error (T0006); previously the field mutability check was missing, allowing struct fields to be mutated through an immutable binding (METEL-119)

Breaking changes:

  • array_push and array_len are removed as top-level built-in functions; use List<T> for mutation and .len() on arrays and lists
  • Code that previously relied on &mut x or p.field = v with a non-let mut binding will now fail typechecking

v0.7.0

Language quality, pointer semantics, closure stabilisation, and aspect bounds. Shipped from sprint/17 and METEL-75–82 (hotfix batch).

Breaking changes:

  • Anonymous closure expressions now use (...) -> ... { ... }; fun(...) is no longer accepted in expression position, and function types are written as (T) -> U (RFC-0041)
  • Struct fields are module-private by default; cross-module field access and construction now require pub on each exposed field (RFC-0032)
  • Mutable bindings now use let mut; standalone mut x = value; is no longer accepted, and for / for-in bindings use the same let mut form (RFC-0042)

New language features:

  • Explicit receiver semantics — methods may declare &self (shared read) or &mut self (shared mutable) receivers; &mut self mutations are visible to the caller without a writeback convention (RFC-0044, METEL-112)
  • Regular and mutable pointer types&expr and &mut expr produce Pointer<T> and MutPointer<T> values; assignment through *ptr and function-pointer auto-deref are supported (RFC-0043, METEL-111)
  • Aspect bounds on generic type parameters — functions, structs, and enums may now declare aspect bounds on their type parameters; bounds are enforced by the typechecker and violation is error T0012 (RFC-0002, RFC-0034, RFC-0035, RFC-0040, METEL-57, METEL-60, METEL-67, METEL-84–93):
    • Inline single bound: fun foo<T: Comparable>(x: T), struct SortedList<T: Comparable>
    • Inline multi-bound with +: fun foo<T: Comparable + Printable>(x: T) (RFC-0034)
    • where clause: fun foo<T>(x: T) where T: Comparable + Printable
    • impl Aspect anonymous parameters: fun foo(x: impl Display) (RFC-0035)
    • Aspect methods declared by a bound are available on the type parameter inside the function body
    • T0012 is emitted at the call/construction site with span on the offending argument
  • String interpolation (${expr}) — string literals may contain ${…} placeholders; each hole desugars to .to_string() concatenated with surrounding fragments via + (RFC-0010, METEL-81, METEL-82)
  • String concatenationString + String -> String (METEL-78)
  • Aspect default methods — an aspect method may provide a default body; impl blocks may omit defaulted methods and inherit them automatically (METEL-77)
  • Self in impl signaturesSelf may be used as a parameter or return type in impl method signatures (METEL-79)
  • Match arm blocks — match arm bodies may be a block in addition to a bare expression (RFC-0018, METEL-78)

Bug fixes:

  • Computed index assignment (arr[i + 1] = v, s.data[offset * 2] = v) now works correctly; previously any computed index expression caused an internal error (METEL-106)
  • &mut self methods on nested struct fields now mutate in place (METEL-112)
  • impl methods with T-typed parameters on generic structs now resolve correctly in Pass 2 (METEL-92)
  • Bounded type parameter method dispatch correctly enforces arity and argument types (METEL-93)
  • ? (error propagation) — routed through From-based coercion; typechecker emits T0007 when no From impl exists (METEL-80)
  • Generic functions returning an ascribed None : Perhaps<T> now correctly constrain the inferred return type (METEL-76)

Tooling:

  • CLI version is derived from CARGO_PKG_VERSION rather than a hardcoded string (METEL-100)
  • Source file extension corrected to .mtl throughout public docs (METEL-100)
  • mod and use removed from the reserved keyword list (METEL-75)

Spec clarifications:

  • pub is not valid on top-level let or mut bindings (METEL-99)

v0.6.4

Module system technical debt. Shipped by Sprint 15 (sprint/15).

Internal improvements:

  • TypeDefinitionRegistry is now used as the cross-module type accumulator in check_graph, replacing the Vec<Decl> approach that cloned raw AST nodes; cross-module struct field type references now resolve correctly even when the field type comes from an indirect dependency (ADR-0032, METEL-3)
  • InferContext::new accepts imported_schemes directly, enforcing the dual-registration invariant (inference + construction passes both see imported names) at the type level (ADR-0022, METEL-6)
  • declared_names map added to ResolvedNames during name resolution, replacing an O(n) AST scan in build_import_schemes for T0009/T0003 distinction (METEL-4)
  • resolve_path_root extracted to src/module_paths.rs as a single shared implementation for both module_loader and name_resolver; fixed a regression where the Name path root incorrectly doubled the module name segment (ADR-0023, METEL-7)
  • StdPrelude::schemes() / evaluator builtin parity assertion added as a compile-time-checked test (ADR-0027, METEL-5)

Compatibility:

  • No language-visible changes.

v0.6.3

Module system — feature complete. Shipped by Sprint 14 (sprint/14).

Bug fixes:

  • return and break are now valid as bare match arm bodies without enclosing braces: arm => return value (#226)
  • Diamond module dependencies (same physical file reachable via two different logical paths) no longer fail with T0003; the name resolver now dereferences path aliases to their canonical form (#228)

Internal improvements:

  • ? operator desugared in a pre-pass (path_normalizer::desugar_propagate_error) rather than carried through inference and construction; Expr::PropagateError no longer exists after normalization (ADR-0030, #214)
  • Type::Perhaps and Type::Result convenience variants removed from the Type enum; both types are now represented uniformly as Type::Named("Perhaps", ...) and Type::Named("Result", ...) (#150)
  • Per-module isolated runtime environments validated with cross-module closure-capture and mutual-recursion tests (#189)
  • All aspect method dispatch key construction routed through ImplMethodKey::to_env_key(), eliminating ad-hoc format strings in the evaluator (#209)

Compatibility:

  • No language-visible changes except the match arm body fix (#226), which is purely additive.

v0.6.2

Evaluator normalization. Shipped by Sprint 13 (sprint/13).

Internal improvements:

  • Value::Perhaps and Value::Result dedicated variants removed; all Perhaps and Result values now use the general Value::Enum { name, variant, fields } representation, eliminating special-case dispatch throughout the evaluator (ADR-0028, #205)
  • evaluate_graph now initialises each module in its own isolated Environment seeded with builtins and cross-linked via the imported_names table populated by check_graph; replaces the flat-merge strategy from v0.5.0 (ADR-0029, #210)

Compatibility:

  • No language-visible changes. All existing programs produce identical output.

v0.6.1

Type system cleanup and std::core virtual module. Shipped by Sprint 12 (sprint/12).

Internal improvements:

  • Unified TypeDefinitionRegistry replaces four separate flat maps (struct_env, method_env, enum_env, aspect impls) in the type inference and construction passes; a single registry instance is now the source of truth for all type and impl data (#133)
  • ImplMethodKey enum replaces flat string concatenation for impl method dispatch keys in the evaluator
  • StdPrelude::default() is the single source of truth for all built-in function schemes, eliminating the previous divergence between the inference and construction registries

New language features:

  • std::core virtual module: Perhaps, Result, Display, Iterable, From, and all built-in functions are available in every module without any explicit import (#201, #202)
  • Glob import tiers: the runtime auto-imports std::core at Std tier (lowest priority); user import path::* declarations use User tier and silently win over Std tier without a conflict error (#206)

Compatibility:

  • All existing programs are unaffected; std::core names that were previously available globally continue to work without import statements

v0.6.0

Module semantics. Shipped by Sprint 11 (sprint/11).

Enforced module semantics (previously deferred from v0.5.0):

  • Visibility enforcement: pub is required for a declaration to be importable; private items produce a compile-time error (T0009) when referenced from another module
  • Import scoping: only names brought in scope by import are accessible; accessing an undeclared name is a compile-time error (T0003)
  • Alias resolution: import mod::name as alias makes alias callable and removes name from scope
  • Import conflict detection: two imports binding the same local name produce a compile-time error (T0011); explicit imports silently win over conflicting glob imports
  • Glob visibility filtering: import mod::* now includes only pub items from the source module; private items are excluded
  • Re-export propagation: names re-exported via export are part of the facade module's public API and importable by consumers without importing the underlying module directly
  • pub declarations require complete type annotations (T0010): every parameter and the return type must be annotated on a pub fun

Internal improvements:

  • Name resolver wired into the type-checking pipeline (load_root → resolve → normalize → check_graph → evaluate_graph)
  • Flat-merge compatibility shim (ADR-0019) and last-segment fallback (ADR-0020) removed
  • root::, self::, and super:: path roots now compute correct module paths in both the loader and name resolver

Compatibility:

  • Single-file programs and programs using only pub items across module boundaries are unaffected
  • Programs that imported private items or relied on global declaration visibility will need pub annotations added

v0.5.0

Module system. Shipped by Sprint 9 (sprint/9).

New language features:

  • Multi-file programs: each .mtl file is a module; the module graph is built from import declarations
  • import path::Name; both loads the referenced file and brings Name into scope
  • Import forms: single name, alias (as), group ({A, B}), glob (*), module handle
  • export path::Name; re-exports a name from a submodule into the current module's public API
  • pub on fun, struct, enum, and aspect marks declarations as externally accessible
  • Absolute and relative path roots: root::, std::, self::, super::
  • Fully-qualified paths valid in type and expression position without a preceding import
  • Circular imports detected at load time with a full chain in the error message
  • Facade modules: parser.mtl alongside parser/ directory — no special mod.mtl file
  • File-to-module mapping via ::/ with no special cases

Shipped in v0.6.1:

  • std::core auto-import and standard library core types (#150, #201, #202)

Compatibility:

  • Single-file programs with no import or export declarations remain valid without modification

v0.4.2

Evaluator refactor, test restructure, and keyword cleanup. Shipped by Sprint 8 (sprint/8).

Breaking changes:

  • Perhaps::Nope renamed to Perhaps::None; the standalone nope keyword is now None

v0.4.1

Technical debt, bug fixes, and internal cleanup. Shipped by Sprint 7 (sprint/7).

Bug fixes:

  • TypeErrorCode::T0005 ("Invalid operand types") is now emitted for arithmetic operators (+, -, *, /, %) applied to non-numeric types (e.g. true + false is now a type error)
  • Unary negation (-) on non-numeric types is now a type error
  • Ordering comparisons (<, <=, >, >=) on non-comparable types (non-numeric, non-String) are now type errors
  • Pattern::Nope latent bug eliminated — nope values are now exclusively Value::Perhaps(None), so the pattern can no longer silently miss the Value::Enum { name: "Perhaps", variant: "Nope" } form

Internal improvements:

  • Value::YoloResult renamed to Value::Result; Perhaps and Result values are now first-class runtime variants — no longer stored as Value::Enum
  • Large enum variants boxed in Decl, Stmt, TypedDecl, TypedStmt (stack frame sizes reduced from 896–1040 bytes to 8 bytes)
  • Dead utility methods removed (Program::new, Type::is_numeric, Type::is_unit); reserved fields annotated with #[allow(dead_code)]
  • All clippy style/idiom warnings resolved

v0.4.0

Aspects and upgraded builtins. Shipped by Sprint 6 (sprint/6).

New language features:

  • Aspect declarations — aspect Foo { fun method(self) -> T; }
  • impl Aspect for Type blocks with method dispatch via .method() syntax
  • Iterable<T> aspect — user-defined types usable in for-in loops
  • From<S> aspect — as cast desugars to T::from(value); user-defined casts for any type pair
  • Display aspect — .to_string() on i64, f64, boolean, String; print/println polymorphic via Display
  • ? operator now supports cross-type error coercion: if the function's error type E2 implements From<E1>, ? calls E2::from(e) automatically

Builtin changes:

  • print(v) and println(v) are now polymorphic (<T: Display>) — accept any Display type
  • i64::from(f: f64) and f64::from(n: i64) built-in From impls replace the hardcoded as special case
  • Deprecated: print_int, println_int, print_float, println_float, int_to_string, float_to_string, bool_to_string (use .to_string() and polymorphic print/println)

Bug fixes:

  • Keyword-prefix identifiers (break_sum, return_value, let_x) now parse correctly as identifiers
  • Multiple impl From<X> for Y blocks with different source types now dispatch independently

v0.3.0

Generics and type-inference improvements. Shipped by Sprint 5 (sprint/5).

New language features:

  • User-defined generic functions — fun id<T>(x: T) -> T — monomorphised at each call site
  • User-defined generic structs — struct Box<T> { value: T }, struct Pair<A, B> { ... }
  • User-defined generic enums — enum Maybe<T> { Some { value: T }, None {} }
  • Let-polymorphism — unannotated let-bound closures are generalised to polymorphic schemes (let id = fun(x) { x } works at i64, boolean, and String in the same scope)
  • Braceless if body — if (c) expr and if (c) a else b (RFC-0022)
  • struct and enum declarations are allowed inside function bodies

Type-inference improvements:

  • expected_ty propagates into match arm bodies — bare [] and nope resolve without ascription when the surrounding return type is known
  • Callee parameter types propagate into argument construction — find(words, nope) resolves without ascription when the parameter type is Perhaps<String>
  • Lvalue path assignment — obj.field = val and arr[i] = val work on non-bare receivers (e.g. get_foo().bar = 1)

v0.2.0

Evaluator improvements, DX features, and language quality fixes. Shipped by Sprint 3 (sprint/3).

New language features:

  • Type ascription operator :[] : i64[] guides type inference without runtime cost (RFC-0021)
  • Shorthand struct field initialisation — Point { x, y } desugars to Point { x: x, y: y }
  • Trailing commas allowed in function parameter lists and argument lists

New built-in functions:

  • assert(cond: boolean) — panics with "assertion failed" if cond is false
  • assert_msg(cond: boolean, msg: String) — panics with msg if cond is false
  • dbg<T>(v: T) -> T — prints [dbg] <value> to stderr and returns the value unchanged
  • print_int(n: i64), println_int(n: i64) — print an i64 without/with newline
  • print_float(f: f64), println_float(f: f64) — print a f64 without/with newline

Bug fixes:

  • Arrays now have value semantics — binding an array to a new variable produces an independent copy
  • Error spans now report file:line:col instead of raw byte offsets
  • Complex expressions (field access, calls) are now valid array index operands

Developer experience:

  • Runtime panics now include a call-stack trace showing function name and call site

v0.1.0

Initial language version. Implemented by the tree-walk interpreter.

Features included:

  • Primitive types: i64, f64, boolean, String, ()
  • Variables: let (immutable), mut (mutable), lexical scoping, fun/type hoisting
  • Functions: first-class values, closures with mutable capture, ? operator (exact error type match only)
  • Structs: literals, field access, methods (impl), mut self, associated functions
  • Enums: unit and struct-like variants, impl blocks
  • Built-in generic types: Perhaps<T>, Result<T, E>, Array<T> / T[] (as special cases; user-defined generics are v0.3.0)
  • Exhaustive pattern matching: all pattern kinds (see Pattern Kinds)
  • Control flow: if/else, while, for, for-in (arrays and ranges only), loop, break/continue, return
  • Type casting: as for i64 ↔ f64
  • Never type (!)
  • Tuples
  • Built-in functions (see Built-in Functions)

Not included (v0.3.0+):

  • User-defined generic functions and types (see Generics)
  • User-defined aspects and impl Aspect for Type (see Aspects)
  • From-based ? coercion across different error types (see The ? Operator)
  • User-defined Iterable<T> implementations (see For-In)