Library ports: get hiccup running, verify malli (reader + interop fixes) (#127)

* Reader: #() params survive syntax-quote (auto-gensym names)

#(...) named its synthesized params with bare gensyms, so a #() written inside a
syntax-quote had its params qualified to the current ns by sq-symbol — and a
qualified symbol isn't a valid fn param. hiccup's compiler emits
`(let [sb# ..] (iterate! #(.append sb# %) ..)), which broke with "Unable to
resolve symbol: ns/_NNNN".

Name the params with a trailing # (auto-gensym suffix, like Clojure's p1__N#) so
syntax-quote maps them consistently and leaves them unqualified. Harmless outside
a backtick (just a regular symbol name).

* interop: String/valueOf static + String is a CharSequence

Two interop gaps surfaced bringing up hiccup and malli:
- String/valueOf(Object): hiccup's compiler stringifies attribute values with
  (String/valueOf (or arg "")). Added the static — "null" for nil, else core-str.
- (instance? CharSequence s) returned false for a string; String implements
  CharSequence, and malli's :re validator gates on it before matching, so :re
  schemas always failed. instance-check now answers true for strings.

---------

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-15 19:36:13 +00:00 committed by GitHub
parent d223f40c91
commit 61d621521b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 38 additions and 3 deletions

View file

@ -707,6 +707,10 @@
"Double" (number? val)
"String" (string? val)
"java.lang.String" (string? val)
# String implements CharSequence — malli's :re validator gates on
# (instance? CharSequence x) before matching (jolt-ltwk).
"CharSequence" (string? val)
"java.lang.CharSequence" (string? val)
"Boolean" (or (= true val) (= false val))
"Keyword" (keyword? val)
# regex patterns (cuerdas-style (instance? Pattern x) checks)

View file

@ -288,6 +288,12 @@
# (String. bytes) / (String. bytes charset): UTF-8 bytes to string.
(each nm ["String" "java.lang.String"]
(register-class-ctor! nm (fn [x &opt charset] (string x))))
# String statics. valueOf(Object) yields "null" for nil, else toString —
# hiccup's compiler emits (String/valueOf (or arg "")) to stringify attribute
# values; core-str gives jolt's str semantics for keywords/numbers/etc.
(each nm ["String" "java.lang.String"]
(register-class-statics! nm
@{"valueOf" (fn [x &opt off cnt] (if (nil? x) "null" (core-str x)))}))
# java.net.URL: enough for selmer's template cache — file: URLs only.
# A protocol-less spec throws (selmer catches MalformedURLException and
# prepends file:///), and getPath hands back a stat-able filesystem path.

View file

@ -448,10 +448,15 @@
nil))
(scan-pct form)
# One canonical gensym per slot 1..max-n (placeholders for unused), plus rest.
# Param names carry a trailing `#` (auto-gensym suffix, matching Clojure's
# `p1__N#`) so a #() written inside a syntax-quote survives: sq-symbol treats
# `#`-suffixed names as auto-gensyms (mapped consistently, left unqualified)
# rather than qualifying them to the current ns — a qualified symbol is not a
# valid fn param. Harmless outside a backtick (just a regular symbol name).
(def slot-syms @{})
(var i 1)
(while (<= i max-n) (put slot-syms i (sym (string (gensym)))) (++ i))
(def rest-sym (if has-rest (sym (string (gensym))) nil))
(while (<= i max-n) (put slot-syms i (sym (string (gensym) "#"))) (++ i))
(def rest-sym (if has-rest (sym (string (gensym) "#")) nil))
# Pass 2: replace each %-symbol with its slot's gensym.
(defn- replace-pct [f]
(cond