Skip to content

Geblang 1.32.0

Choose a tag to compare

@dwgebler dwgebler released this 07 Jul 10:31

Changed

  • geblang test now executes on the bytecode VM by default, the same backend
    release builds ship, so a test that passes only on the tree-walking evaluator
    no longer hides a VM-path regression. A test file the VM cannot run fails
    loudly, naming the file, instead of silently falling back. --runtime=evaluator
    (alias --disable-vm) runs the whole suite on the evaluator. A file covering a
    documented, temporarily-accepted divergence may carry a # @vm-divergence: <key>
    comment plus a matching KNOWN_DIVERGENCES.md row to run on the evaluator; the
    runner cross-validates the two and reports how many files ran that way.
  • instanceof is now module-exact: the right-hand side resolves to the
    specific class or interface declaration named in scope, and an instance
    matches when its actual class is that declaration or a subtype of it. Two
    same-named classes from different modules no longer match each other.
    typeof(x) == SomeClass comparisons are unchanged (still by name). The
    types chapter documents the distinction.
  • Deferred calls now evaluate their callee, receiver, and arguments when the
    defer statement executes, on both backends, as the documentation always
    stated. A variable mutated after the defer statement no longer changes what
    the deferred call sees; a defer inside a loop captures each iteration's
    values.
  • Passing arguments to a parent constructor that does not exist is now an error
    on both backends: parent(args) where the base class declares no constructor
    raises a catchable RuntimeError. Previously one runtime silently ignored the
    arguments. Zero-argument parent() to such a base is still a no-op, and a base
    with a matching constructor is unchanged.

Added

  • Methods are first-class values: let f = SomeClass.staticMethod and
    let m = instance.method produce callable values that can be stored,
    passed, returned, deferred, and used as callbacks (xs.sortBy(obj.key)),
    locally and across modules. A bound method keeps its receiver by reference.
  • defer f(...xs) accepts a list spread; the list reference freezes at the
    defer statement and its elements are expanded when the call fires.
  • Modules can export overloaded functions: mod.over(5) and mod.over("x")
    select by argument types across the module boundary, in every form -
    qualified, from-import, as a stored value, with named arguments, deferred,
    and as a callback. Previously a module declaring two overloads of an
    exported function failed to compile on the standard runtime.
  • An overloaded async function called through a stored value or an
    imported overload set returns a Task like a direct call does; in a
    mixed set the selected overload decides (async yields a Task, sync
    returns its value). Previously the body ran synchronously and returned
    the plain result.

Fixes

  • An error thrown inside a callback passed to a native higher-order function
    (sortBy, map, filter, reduce, and the collections equivalents) and
    caught by a surrounding try/catch no longer corrupts the VM: iteration stops
    at the throwing element and execution continues cleanly after the catch,
    matching the evaluator.
  • An uncaught error thrown inside such a callback now keeps the full caller
    chain in the VM stack trace.
  • A fault raised by a deferred call or a del-fired destructor whose body
    lives in another module is reported once with a clean message on both
    backends, instead of nesting a fully rendered error inside the failure text.
  • The == operator and assertEquals now share one canonical equality
    definition, pinned by guard tests on both backends: comparing native handle
    values (for example two file-serve descriptors) no longer panics, and
    assertEquals agrees with == for complex numbers.
  • web.http: a file response built by serveFile survives
    http.responseFrom(...), so builder-style wrapping preserves the streamed
    file.
  • A typed error thrown inside a callback or a decorated function, method, or
    constructor is catchable by its class on the VM (catch (ValueError e)
    now matches; previously the class was lost and the error rendered twice).
  • Closures capture outer variables referenced through string interpolation,
    pipelines, partial application, comprehensions, match-case patterns, and
    destructuring assignments; on the VM these previously read wrong values or
    failed with "local is undefined".
  • Named arguments on a decorated function or method bind to the original
    declared parameter names on both backends; decorators are transparent to
    callers.
  • A class inheriting __next/__done or __iter from a class in another
    module iterates with for-in on the VM.
  • defer accepts module-qualified functions (defer mod.cleanup()) and
    nested-selector static methods on the VM.
  • del works on variables whose class is declared in another module.
  • Cross-module interface hierarchies (Sub extends Base in another module)
    match instanceof Base on the VM.
  • Running a test file's test.run on the VM reports failures with the same
    clean class-prefixed message and frames as geblang test.
  • A spread argument works in any position (f(1, ...rest, 4)), combines
    with named arguments (f(c: 1, ...rest)), and multiple spreads expand in
    source order; previously arguments after a spread were silently dropped
    and named arguments before a spread were silently treated as positional.
    Native functions reject named arguments with spread with a clear error.
  • An uncaught error thrown from a callback declared in another module, a
    capturing closure, or a callable object passed to a native higher-order
    function now keeps the full caller chain in the stack trace, identical on
    both backends. The reverse direction is also complete: when a function
    from another module passes your callback to a higher-order method, that
    function's own frame appears in the trace and in caught stackTrace()
    frames.
  • Named arguments work on functions imported from another module
    (mod.f(b: 2, a: 1)), including out-of-order names, mixes with positional
    and spread arguments, omitted defaults, from-import forms, and deferred
    calls; previously these were rejected at runtime.
  • Named arguments also work when constructing a class from another module
    (mod.Point(y: 2, x: 1)), including overloaded constructors, from-import
    and aliased forms, dict spreads, defaults, and deferred construction.
  • A destructor (func ~ClassName()) on a class from another module fires at
    del or program exit like a local class does; previously it could fire
    eagerly right after construction, and del never invoked it. Exit-sweep
    ordering (reverse creation), del-fires-once, and destructors that construct
    further destructible values behave identically on both backends.
  • An uncaught error thrown from a decorated function, method, or static method
    renders an identical stack trace on both backends: the wrapper frame is named
    after the decorated function and the top-level frame keeps its line. A class
    decorator's uncaught throw also keeps the top-level line.
  • A DECORATED method used as a first-class value runs its decorator wrapper
    with the receiver bound, on both backends: let f = s.handle; f(5) behaves
    like s.handle(5), including in callbacks and defers. Previously the call
    failed with a wrong-arity error, and a decorated static method taken as a
    value silently skipped its decorator.
  • profile.elapsed and metrics.duration now return the documented
    milliseconds as a float with sub-millisecond precision (previously raw
    nanoseconds as an integer). The now() token they take is unchanged.
  • Variables mutated inside a try body keep their values when an exception
    unwinds to the handler, and finally effects always run; previously the
    standard runtime rolled the frame's locals back to their try-entry values.
  • A select statement inside a closure captures its channels; previously a
    worker running such a closure could read an undefined value and block
    forever.
  • Calling a method on an instance whose class is exported and overridden in
    another module dispatches to the override; previously a statically
    resolved call could run the base method.
  • A cross-module named-argument call may omit a defaulted parameter between
    two supplied ones (f(a: 1, c: 3) with b defaulted).
  • reflect.parameters and reflect.returnType work on function values,
    including functions passed across module boundaries; reflect.fields
    reports declared field types and lossless decorator arguments (floats and
    lists included) for cross-module instances; reflect.interfaces returns
    bare interface names on both backends.
  • reflect.class(name) prefers the program's own classes over a same-named
    class exported by a standard-library module.
  • test.mock patches apply inside test methods on the standard runtime and
    restore automatically between methods.

Performance

  • redis: the reply reader buffers network chunks in linear time, so large
    bulk replies no longer copy quadratically (a 16 MB reply drops from roughly
    885 ms to 41 ms), and getBytes returns the raw payload without a
    decode and re-encode round trip.