Skip to content

Commit b5751d0

Browse files
committed
Emit JVM-compatible case* from case macro
The case macro now expands to (let* [G e] (case* G shift mask default imap ...)) matching JVM Clojure's format. This enables code-walking tools like riddley and cloverage that have a case-handler expecting the JVM case* structure.
1 parent b45148c commit b5751d0

File tree

6 files changed

+53
-42
lines changed

6 files changed

+53
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SCI is used in [babashka](https://github.com/babashka/babashka),
1212

1313
## Unreleased
1414

15+
- `case` now macroexpands to JVM-compatible `case*` format, enabling tools like riddley and cloverage to work with SCI
1516
- Fix `.method` on class objects (e.g. `(.getDeclaredField String "value")`) routing to static instead of instance method path
1617
- Support [#564](https://github.com/babashka/sci/issues/564): `this-as` in ClojureScript
1718

src/sci/impl/analyzer.cljc

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -839,43 +839,20 @@
839839
(throw-error-with-location "Too many arguments to if" expr))))
840840

841841
(defn analyze-case*
842+
;; JVM case* format: (case* ge shift mask default imap switch-type check-type skip-check)
843+
;; imap: {key [test-constant result-expr], ...}
842844
[ctx expr]
843-
(let [ctx-wo-rt (without-recur-target ctx)
844-
case-val (analyze ctx-wo-rt (second expr))
845-
clauses (nnext expr)
846-
match-clauses (take-nth 2 clauses)
847-
result-clauses (analyze-children ctx (take-nth 2 (rest clauses)))
848-
[default? case-default] (when (odd? (count clauses))
849-
[true (analyze ctx (last clauses))])
850-
cases (interleave match-clauses result-clauses)
851-
assoc-new (fn [m k v]
852-
(if-not (contains? m k)
853-
(assoc m k v)
854-
(throw-error-with-location (str "Duplicate case test constant " k)
855-
expr)))
856-
case-map (loop [cases (seq cases)
857-
ret-map {}]
858-
(if cases
859-
(let [[k v & cases] cases]
860-
(if (seq? k)
861-
(recur
862-
cases
863-
(reduce (fn [acc k]
864-
(assoc-new acc k v))
865-
ret-map
866-
k))
867-
(recur
868-
cases
869-
(assoc-new ret-map k v))))
870-
ret-map))
871-
f (if default?
872-
(sci.impl.types/->Node
873-
(eval/eval-case ctx bindings case-map case-val case-default)
874-
nil)
875-
(sci.impl.types/->Node
876-
(eval/eval-case ctx bindings case-map case-val)
877-
nil))]
878-
f))
845+
(let [[_ ge _shift _mask default imap] expr
846+
ctx-wo-rt (without-recur-target ctx)
847+
case-val (analyze ctx-wo-rt ge)
848+
case-default (analyze ctx default)
849+
case-map (reduce-kv
850+
(fn [m _k [test result]]
851+
(assoc m test (analyze ctx result)))
852+
{} imap)]
853+
(sci.impl.types/->Node
854+
(eval/eval-case ctx bindings case-map case-val case-default)
855+
nil)))
879856

880857
(defn analyze-try
881858
[ctx expr]
@@ -1457,8 +1434,7 @@
14571434
def (analyze-def ctx expr)
14581435
loop* (analyze-loop* ctx expr)
14591436
if (return-if ctx expr)
1460-
;; case macro expands into case* with no changes via fast-path
1461-
(case case*) (analyze-case* ctx expr)
1437+
case* (analyze-case* ctx expr)
14621438
try (analyze-try ctx expr)
14631439
throw (analyze-throw ctx expr)
14641440
expand-dot* (expand-dot* ctx expr)

src/sci/impl/copy_vars.cljc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
inline (contains? inlined-vars sym)
4747
fast-path (or (= 'or sym)
4848
(= 'and sym)
49-
(= 'case sym)
5049
(= 'ns sym)
5150
(= 'lazy-seq sym))
5251
varm (merge (cond-> {:name (or nm (list 'quote (symbol (name sym))))}

src/sci/impl/namespaces.cljc

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -999,8 +999,34 @@
999999
(if and# (and ~@next) and#))))
10001000

10011001
(defn case**
1002-
"This is a macro for compatiblity. Only there for docs and macroexpand, faster impl in analyzer.cljc"
1003-
[_ _ & body] `(case* ~@body))
1002+
[_ _ e & clauses]
1003+
(let [ge (gensym)
1004+
ex-class #?(:clj 'java.lang.IllegalArgumentException :cljs 'js/Error)
1005+
default (if (odd? (count clauses))
1006+
(last clauses)
1007+
`(throw (new ~ex-class (str "No matching clause: " ~ge))))
1008+
clauses (if (odd? (count clauses)) (butlast clauses) clauses)
1009+
pairs (partition 2 clauses)
1010+
expanded (mapcat (fn [[test expr]]
1011+
(if (seq? test)
1012+
(map (fn [t] [t expr]) test)
1013+
[[test expr]]))
1014+
pairs)
1015+
imap (loop [expanded (seq expanded)
1016+
imap {}
1017+
seen #{}
1018+
i 0]
1019+
(if expanded
1020+
(let [[test expr] (first expanded)]
1021+
(when (contains? seen test)
1022+
(throw (#?(:clj IllegalArgumentException. :cljs js/Error.)
1023+
(str "Duplicate case test constant: " test))))
1024+
(recur (next expanded)
1025+
(assoc imap i [test expr])
1026+
(conj seen test)
1027+
(inc i)))
1028+
imap))]
1029+
`(let* [~ge ~e] (case* ~ge 0 0 ~default ~imap :sparse :hash-equiv nil))))
10041030

10051031
(defn loaded-libs** [syms]
10061032
(utils/dynamic-var

src/sci/impl/utils.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@
213213

214214
(def ana-macros
215215
'#{do if and or fn fn* def defn
216-
lazy-seq case try defmacro
216+
lazy-seq try defmacro
217217
expand-dot* expand-constructor new . import in-ns ns var
218218
set! resolve})
219219

test/sci/core_test.cljc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,15 @@
828828
(catch java.lang.IllegalArgumentException e
829829
(throw (Exception. (ex-message e)))))")
830830
:cljs (eval* "(case (inc 2), 1 true, 2 (+ 1 2 3))"))))
831+
(testing "case macroexpands to JVM-compatible case*"
832+
(let [expanded (eval* "(macroexpand '(case x 1 :a 2 :b :default))")]
833+
(is (= 'let* (first expanded)))
834+
(let [body (nth expanded 2)]
835+
(is (= 'case* (first body)))
836+
;; imap is 5th element: {key [test-constant result-expr], ...}
837+
(let [imap (nth body 5)]
838+
(is (map? imap))
839+
(is (every? (fn [[_k v]] (and (vector? v) (= 2 (count v)))) imap))))))
831840
#?(:clj
832841
(testing "case generated by macro"
833842
(is (= :yolo

0 commit comments

Comments
 (0)