cl .sort() silently adopted Python semantics (in-place, returns None, single-arg key): needs a breaking note + a 2-arg-lambda diagnostic
#6,431 opened on Jun 6, 2026
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.jacdefines bothdef jac_rt_list_py()andcl def jac_rt_list_cl()that each donums.sort(key=lambda x: -x)as a statement and return the in-place-mutatednums, asserting native (Python) and cl (JS) produce identical output.sorted(...)is used wherever a returned list is needed.poly_dispatch_complete.cl.jacuseslst.sort()as a statement.- Mechanism: #6307 generalized the poly dispatcher so
_poly_methodsis built from everyemit_*method (sosortis 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
- Docs / migration: add a
breakingrelease note for the cl list method-semantics change (.sort()is now in-place, returnsNone, takes a single-argkey; usesorted()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. - Diagnostic: emit a compile-time warning/error when
.sort()(orsorted(..., key=)) receives a lambda with 2+ parameters, since that is almost always a mistaken JS/TS comparator. Point users tokey=/sorted(). This turns a silent runtime mis-sort/crash into a build-time message.
Environment
- jaclang
mainHEAD2bb7a605c(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)