jaseci-labs/jac

cl .sort() silently adopted Python semantics (in-place, returns None, single-arg key): needs a breaking note + a 2-arg-lambda diagnostic

Open

#6,431 opened on Jun 6, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)Jac (378 forks)auto 404
Missing Docsgood first issue

Repository metrics

Stars
 (555 stars)
PR merge metrics
 (PR metrics pending)

Description

Summary

In cl (client / Jac-to-JS) code, a list .sort() call now follows Python list.sort() semantics: it sorts in place, returns None/undefined, and treats its lambda as a single-arg key function (not a 2-arg JS comparator). This is the correct and clearly intended direction (the cross-backend equivalence fixtures assert it, see below), but it rolled out silently: there is no breaking release note, and there is no compile-time diagnostic when a 2-arg comparator lambda is passed to .sort().

The result is that jac-client apps written against the older behavior (where a loosely-typed .sort(comparator) leaked to native Array.prototype.sort, which uses a comparator and returns the array) now break at runtime with a cryptic error and no warning. This hit a production app (skooch.ai) on the Experience Skooch page and is the same class as the earlier jacBuilder sort fixes.

This issue is not a request to revert the semantics. It asks for: (1) a breaking release note + migration guidance, and (2) a compile-time diagnostic for the 2-arg .sort() footgun.

Reproduction

cl def demo() -> int {
    rows = [{"n": 3}, {"n": 1}, {"n": 2}];
    # JS idiom: comparator + use the return value
    rows = rows.sort(lambda a: any, b: any -> int { return a.n - b.n; });
    return rows.length;   # throws: Cannot read properties of undefined (reading 'length')
}

Emitted JS (current main, jaclang 0.16.0, HEAD 2bb7a605c):

rows = _jac.poly.call(rows, "sort", (a, b) => { return a.n - b.n; });

_jac.poly.call(arr, "sort", fn) dispatches to _jac.poly._list.sort -> _jac.list.sort(a, key) in jac/jaclang/compiler/passes/ecmascript/jac_runtime_js.jac:

sort(arr, key) {
  if (key) {
    arr.sort((a, b) => {
      const ka = key(a), kb = key(b);   // key() called with ONE arg
      return ka < kb ? -1 : ka > kb ? 1 : 0;
    });
  } else { ... }
  // no return -> undefined
}

So at runtime: the assigned variable becomes undefined (then .length/.map throws), and a 2-arg comparator is silently invoked with one arg (so even a statement-form rows.sort(comparator) mis-sorts).

Verified by running the emitted current-main _jac runtime in Node: the snippet above returns undefined and throws; sorted(rows, key=lambda x: any -> any { return x.n; }) returns a correctly ordered new list.

This behavior is intentional (for reference)

The new semantics are the tested cross-backend contract, so this is working as designed:

  • jac/tests/compiler/passes/ecmascript/fixtures/prim_jac_runtime.jac defines both def jac_rt_list_py() and cl def jac_rt_list_cl() that each do nums.sort(key=lambda x: -x) as a statement and return the in-place-mutated nums, asserting native (Python) and cl (JS) produce identical output. sorted(...) is used wherever a returned list is needed.
  • poly_dispatch_complete.cl.jac uses lst.sort() as a statement.
  • Mechanism: #6307 generalized the poly dispatcher so _poly_methods is built from every emit_* method (so sort is auto-included), routing loosely-typed .sort() through _jac.poly.call. The cl-build unification (#6390) shipped it.

The correct migration is to use sorted(arr, key=..., reverse=...) (returns a new list, single-arg key), or call .sort() as a statement with a key= function.

Requests

  1. Docs / migration: add a breaking release note for the cl list method-semantics change (.sort() is now in-place, returns None, takes a single-arg key; use sorted() to get a new list). It currently rode in under a .get() fix (#6307) and a build refactor (#6390) with no semantics note, so the runtime breakage is undiscoverable until an app crashes in production.
  2. Diagnostic: emit a compile-time warning/error when .sort() (or sorted(..., key=)) receives a lambda with 2+ parameters, since that is almost always a mistaken JS/TS comparator. Point users to key= / sorted(). This turns a silent runtime mis-sort/crash into a build-time message.

Environment

  • jaclang main HEAD 2bb7a605c (0.16.0)
  • Worked previously on jac-client 0.3.1 / jaclang ~0.11-0.15 (loosely-typed .sort() emitted native JS sort)
  • Backend: cl (Jac-to-JS)

Contributor guide