Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0a4429d
Add multimethods as opt-in runtime
borkdude Apr 18, 2026
bfd33c1
Extract shared toFn helper into internal.js
borkdude Apr 18, 2026
8061ce4
Make internal.js truly internal
borkdude Apr 18, 2026
5439820
Make cljs.test/report a multimethod
borkdude Apr 18, 2026
df5a87e
Wire multimethod runtime into playground
borkdude Apr 18, 2026
2b0906d
Fix dual-module bug in playground dev rewrite
borkdude Apr 18, 2026
4b39a85
Fix shallow clone in 3-arg derive
borkdude Apr 18, 2026
ba9c71d
CHANGELOG for multimethods + cljs.test/report
borkdude Apr 18, 2026
74ee69f
Add failing repro for stale-cache bug after 2-arg derive
borkdude Apr 18, 2026
f6b36f2
Fix stale cache on 2-arg derive + ancestor leak in _deriveInto
borkdude Apr 18, 2026
71309b4
Address remaining PR review items (#3-#6)
borkdude Apr 18, 2026
38c32fa
Revert #6: keep global hierarchy module-local
borkdude Apr 18, 2026
70cb00d
Isolate derive tests with per-test namespaced keywords
borkdude Apr 18, 2026
3f75f21
Fix #1: parents/ancestors/descendants follow Clojure arg order
borkdude Apr 18, 2026
6270432
Fix #2: prefer-method / isa? / derive use value-equality
borkdude Apr 18, 2026
794c843
Fix #3: cache vector dispatch in MultiFn.getMethod
borkdude Apr 18, 2026
b657d39
Fix #4: drop internal.js, keep __toFn in core.js as a marked-private …
borkdude Apr 18, 2026
bb24610
Fix #5: typeof 'function' over instanceof Function
borkdude Apr 18, 2026
0371cd9
Cache default-dispatch fn on MultiFn
borkdude Apr 18, 2026
5c9c547
Extend fix #2 to hAnd reader
borkdude Apr 18, 2026
002cfe0
Record known perf cliffs from PR review in todo.md
borkdude Apr 18, 2026
1a3fb4d
Drop todo.md — all items resolved, deferred, or tracked elsewhere
borkdude Apr 18, 2026
d7064ff
Trim multimethod entry in CHANGELOG to user-facing copy
borkdude Apr 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

## 0.11.187

- Add multimethod support: `defmulti`, `defmethod`, `get-method`, `methods`,
`remove-method`, `remove-all-methods`, `prefer-method`, `prefers`, and
hierarchy ops `isa?`, `derive`, `underive`, `make-hierarchy`, `parents`,
`ancestors`, `descendants`.
- `cljs.test/report` is now a multimethod, extensible via `defmethod`:
```
(defmethod report [:cljs.test/default :begin-test-var] [m] ...)
```
`test-var` now fires `:begin-test-var` / `:end-test-var` events.
- Accept plain `await` in async functions, in anticipation of CLJS next.
The legacy `js-await` and `js/await` forms continue to work as
aliases for now and may be deprecated in a future version.
Expand Down
10 changes: 8 additions & 2 deletions bb/tasks.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@
(defn bump-core-vars []
(let [core-vars (:out (shell {:out :string}
"node --input-type=module -e 'import * as squint from \"squint-cljs/core.js\";console.log(JSON.stringify(Object.keys(squint)))'"))
parsed (apply sorted-set (map symbol (json/parse-string core-vars)))]
;; Drop names starting with "__" — convention for
;; exported-but-private helpers (e.g. __toFn) that other runtime
;; modules import but user CLJS shouldn't resolve as a core var.
parsed (into (sorted-set)
(comp (remove #(str/starts-with? % "__"))
(map symbol))
(json/parse-string core-vars))]
(spit "resources/squint/core.edn" (with-out-str
((requiring-resolve 'clojure.pprint/pprint)
parsed)))))
Expand Down Expand Up @@ -87,7 +93,7 @@
(shell "node" "node_cli.js" "compile" src)
(let [out (:out (shell {:out :string} "node" out-file))]
(fs/delete out-file)
(assert (str/includes? out "Ran 10 tests containing 20 assertions") out)
(assert (str/includes? out "Ran 11 tests containing 21 assertions") out)
(assert (str/includes? out "1 failures, 0 errors") out)
;; :begin-test-ns must fire for each ns visited by run-tests
(assert (str/includes? out "Testing ns.a") out)
Expand Down
17 changes: 11 additions & 6 deletions doc/cljs-test-todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@ extension point.
`{op-sym (fn [msg form] ...)}`) that the macro consults. Squint can ship
the existing four cases as default methods.

### 5. `report` is hardcoded too
`clojure.test/report` is a multimethod keyed on `:type`; users add
### 5. `report` is hardcoded too ✅ DONE
~~`clojure.test/report` is a multimethod keyed on `:type`; users add
methods to extend reporting (e.g. cljs-test-display). Ours is a single
`case` defn.

**Fix sketch:** convert to a multimethod (works in squint's runtime; we
already use `(use-fixtures ...)` etc.).
`case` defn.~~

Fixed: `report` is now a `defmulti` dispatching on
`[*current-reporter* (:type m)]`, matching cljs.test. The built-in
reporter lives under `:cljs.test/default`; unknown combinations fall
through to a no-op `:default` method. `test-var` now also emits
`:begin-test-var` / `:end-test-var` so reporters can bracket per-var
output. Smoke test `report-is-multimethod-test` covers user-defined
extensions.

### 6. `test-var` is a plain fn
`clojure.test/test-var` is a multimethod (`:default` impl is what
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"src/squint/set.js",
"src/squint/html.js",
"src/squint/math.js",
"src/squint/multi.js",
"src/squint/test.js",
"lib",
"node_cli.js",
Expand Down Expand Up @@ -61,6 +62,7 @@
"./src/squint/string.js": "./src/squint/string.js",
"./node-api.js": "./node-api.js",
"./src/squint/html.js": "./src/squint/html.js",
"./src/squint/multi.js": "./src/squint/multi.js",
"./src/squint/test.js": "./src/squint/test.js"
}
}
2 changes: 1 addition & 1 deletion playground/bb.edn
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
(fs/create-sym-link "public/js/squint-local" (fs/parent (fs/parent (fs/absolutize ".")))))
(let [squint-prod (fs/file "public" "public" "src" "squint")]
(fs/create-dirs squint-prod)
(doseq [source-file ["core.js" "string.js" "set.js" "html.js" "math.js" "test.js"]]
(doseq [source-file ["core.js" "string.js" "set.js" "html.js" "math.js" "multi.js" "test.js"]]
(fs/copy (fs/file ".." "src" "squint" source-file)
squint-prod
{:replace-existing true}))))}
Expand Down
1 change: 1 addition & 0 deletions playground/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"squint-cljs/src/squint/set.js": "./src/squint/set.js",
"squint-cljs/src/squint/html.js": "./src/squint/html.js",
"squint-cljs/src/squint/math.js": "./src/squint/math.js",
"squint-cljs/src/squint/multi.js": "./src/squint/multi.js",
"squint-cljs/src/squint/test.js": "./src/squint/test.js",
"@codemirror/language": "https://ga.jspm.io/npm:@codemirror/language@6.10.0/dist/index.js",
"@codemirror/lang-javascript": "https://ga.jspm.io/npm:@codemirror/lang-javascript@6.2.1/dist/index.js",
Expand Down
14 changes: 12 additions & 2 deletions playground/public/js/main_js.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,18 @@ let evalCode = async (code) => {
// opaque origin that can't resolve relative specifiers. Anchor on the
// page origin — import.meta.url can point to an unexpected bundle path
// under Vite.
const localBase = `${window.location.origin}/js/squint-local/`;
js = js.replaceAll("'squint-cljs/", `'${localBase}`);
//
// Route everything through /src/squint/ (matching the importmap) so
// user-compiled code and the runtime modules (test.js, multi.js) end
// up importing the SAME URL for core.js. Previously user code went
// via /js/squint-local/core.js → Vite-transformed shim →
// /@fs/.../src/squint/core.js, while the runtime's bare specifier
// resolved via the importmap to /src/squint/core.js — two URLs,
// two ES-module instances, two _metaSym symbols, and with-meta
// written through one was invisible to meta in the other.
const base = `${window.location.origin}/src/squint/`;
js = js.replaceAll("'squint-cljs/src/squint/", `'${base}`);
js = js.replaceAll("'squint-cljs/", `'${base}`);
}
JSEditor(js);
if (!repl) {
Expand Down
1 change: 0 additions & 1 deletion resources/squint/core.edn
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
_LT__EQ_
_PLUS_
_STAR_
__protocol_satisfies
_iterator
abs
aclone
Expand Down
29 changes: 27 additions & 2 deletions src/squint/compiler.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
[squint.internal.fn :refer [core-defmacro core-defn core-defn- core-fn]]
[squint.internal.loop :as loop]
[squint.internal.macros :as macros]
[squint.internal.multi :as multi]
[squint.internal.protocols :as protocols]
[squint.internal.test :as test])
#?(:cljs (:require-macros [squint.resource :refer [edn-resource]])))
Expand Down Expand Up @@ -103,7 +104,22 @@
'assoc macros/assoc-inline
'assoc! macros/assoc!-inline
'get macros/get-inline
'vswap! macros/vswap!}
'vswap! macros/vswap!
'defmulti multi/core-defmulti
'defmethod multi/core-defmethod
'get-method multi/core-get-method
'methods multi/core-methods
'remove-method multi/core-remove-method
'remove-all-methods multi/core-remove-all-methods
'prefer-method multi/core-prefer-method
'prefers multi/core-prefers
'isa? multi/core-isa?
'derive multi/core-derive
'underive multi/core-underive
'make-hierarchy multi/core-make-hierarchy
'parents multi/core-parents
'ancestors multi/core-ancestors
'descendants multi/core-descendants}
cc/common-macros))

(def built-in-macro-nss
Expand Down Expand Up @@ -452,6 +468,7 @@
cc/*repl* (:repl opts cc/*repl*)]
(let [core-package (get import-maps cc/*core-package* cc/*core-package*)
need-html-import (atom false)
need-multi-import (atom false)
opts (merge {:ns-state (atom {})
:top-level true} opts)
imported-vars (atom {})
Expand All @@ -478,7 +495,8 @@
:imports imports
:jsx false
:pragmas pragmas
:need-html-import need-html-import))
:need-html-import need-html-import
:need-multi-import need-multi-import))
jsx *jsx*
_ (when (and jsx jsx-runtime)
(swap! imports str
Expand All @@ -500,6 +518,13 @@
(if cc/*repl*
(format "var squint_html = await import('%s');\n" html-pkg)
(format "import * as squint_html from '%s';\n" html-pkg)))))
_ (when @need-multi-import
(swap! imports str
(let [multi-pkg "squint-cljs/src/squint/multi.js"
multi-pkg (get import-maps multi-pkg multi-pkg)]
(if cc/*repl*
(format "var squint_multi = await import('%s');\n" multi-pkg)
(format "import * as squint_multi from '%s';\n" multi-pkg)))))
pragmas (:js @pragmas)
imports (when-not elide-imports @imports)
exports (when-not elide-exports
Expand Down
Loading
Loading