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

オープン

#6,431 opened on 2026/06/06

 (0 件のコメント) (0 件のリアクション) (0 人の担当者)Jac (378 件のフォーク)auto 404
Missing Docsgood first issue

Repository metrics

Stars
 (555 個のスター)
PR merge metrics
 (PR metrics pending)

説明

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)

コントリビューターガイド