Should tagged literals work the same in shadow-cljs? I'm in the process of migrating malli's test suite from cljs-test-runner to shadow, and I'm getting a cryptic error from a tagged literal. The same file compiles fine under cljs-test-runner (which invokes the cljs compiler directly). Details in ๐งต
Multiple files failed to compile.
------ ERROR -------------------------------------------------------------------
File: /home/joel/work/metosin/malli/test/malli/parser_test.cljc:135:25
--------------------------------------------------------------------------------
132 | (deftest and-complex-parser-test
133 | (is (= {} (m/parse [:and :map [:fn map?]] {})))
134 | (is (= {} (m/parse [:and [:fn map?] :map] {})))
135 | (is (= #malli.core.Tag{:key :left, :value 1} (m/parse [:and [:orn [:left :int] [:right :int]] [:fn number?]] 1)))
-------------------------------^------------------------------------------------
malli.core.Tag
--------------------------------------------------------------------------------
136 | (is (= #malli.core.Tag{:key :left, :value 1} (m/parse [:and [:fn number?] [:orn [:left :int] [:right :int]]] 1)))
137 | (is (= 1 (m/parse [:and {:parse/transforming-child :none} [:fn number?] [:orn [:left :int] [:right :int]]] 1)))
138 | (is (= 1 (m/parse [:and :int [:or :int :boolean]] 1)))
139 | (is (= 1 (m/parse [:and [:or :int :boolean] :int] 1)))
--------------------------------------------------------------------------------
------ ERROR -------------------------------------------------------------------
File: /home/joel/work/metosin/malli/test/malli/core_test.cljc:3614:26
--------------------------------------------------------------------------------
3611 | :message nil}]}
3612 | (with-schema-forms
3613 | (m/explain [:andn [:m :map] [:v [:vector :any]]] {}))))
3614 | (is (= #malli.core.Tags{:values {:m {} :f {}}}
--------------------------------^-----------------------------------------------
malli.core.Tags
--------------------------------------------------------------------------------
3615 | (m/parse [:andn [:m :map] [:f [:fn map?]]] {})))
3616 | (let [s [:andn [:m :map] [:f [:fn map?]]]]
3617 | (is (= {} (->> {} (m/parse s) (m/unparse s)))))
3618 | (is (= #malli.core.Tags{:values {:o #malli.core.Tag{:key :left, :value 1}, :f 1}}
--------------------------------------------------------------------------------where are they defined?
malli.core has
(defrecord Tag [key value])there's no explicit definition of the tagged literal
then its not a tagged literal is it? ๐
record literal? honestly, I never use this syntax myself so I don't know what to call it
I honestly don't know what it works. doesn't seem like it should
Yeah I'm a bit surprised but it seems to work in CLJ and under cljs-test-runner!
can't say to be honest. not quite sure what the point of using reader literals here is either. I mean its code, just use the records directly ๐คท
if you make a smaller repro to show that is works in cljs.main compile but not shadow-cljs I can take a look
too much going on in the malli repo to easily reproduce
Yeah for sure, I wasn't expecting you to debug it for me! I asked because I wondered if there's some known gotcha that I'm missing.
Switching to record constructors instead of # is a good idea. I'll also try the minimal repro.
#malli.core.Tags{:values {:m {} :f {}}} is just (core/->Tags {:m {} :f {}}) or whatever the structure is
yep
fyi: the clojure reference has a couple of references to that reader notation > deftype/defrecord can be written with a special reader syntax #my.thing[1 2 3] where: ... > defrecord supports an additional reader form of #my.record{:a 1, :b 2} taking a map that initializes a defrecord according to:... etc
yeah, not saying that this isn't a bug in shadow-cljs, but it is kind of complicated given how that whole business is implemented
fairly certain it only works because its .cljc and won't work in plain .cljs, but could be entirely wrong on that one too
my rule of thumb is to not use record literals in code ever. tagged literals are great for data, but just complicated to no end in code
I've been avoiding them as well due to getting bitten too many times
thanks for the quote @lasse.olavi.maatta, I was trying to find them described in https://clojure.org/reference/reader
Interesting, trying to reproduce this on cljs.main gives me "class not found: record.Foo". So now the mystery is why is this working in malli!
there is some special REPL behavior where it works differently there. maybe something to do with that
or as I said cljc vs cljs
repro was cljc as well
maybe forgot the (:require-macros [repro]) for cljs part? as in making the clj side never loaded?
ah yes malli.core has :require-macros
thanks for confirming my suspicion ๐
still didn't get the repro to work
procrastinating by reading the jvm clj impl, here's the relevant branch: https://github.com/clojure/clojure/blob/8ae9e4f95e2fbbd4ee4ee3c627088c45ab44fa68/src/jvm/clojure/lang/LispReader.java#L1430
and this is the relevant code in cljs: https://github.com/clojure/clojurescript/blob/6c7a1680b29e9215e95d867c89f54f2107806220/src/main/clojure/cljs/vendor/clojure/tools/reader.clj#L869-L870
shadow-cljs uses the same reader impl ๐คท
Yeah! So I guess the difference between malli & my repro is that the ns defining the record hasn't been loaded in clj mode, even though I have :require-macros.
if you did it correctly that means it will be loaded
but it might be a loading order issue. meaning if its in the same file then it might only apply to files read/compiled after that
eg. your malli.core gets loaded before malli.core-test. so core-test can use the literal but core can't
just guessing blindly really
I know I wrote some custom code for literals but can't find it
I'll keep trying to repro, loading order sounds like a potential culprit. I still haven't gotten any version of this to compile in CLJS.
found it ๐
FWIW this is my current status
% cat src/record.cljc
(ns record
#?(:cljs (:require-macros [record])))
(defrecord Foo [field])
% cat src/repro.cljc
(ns repro
#?(:cljs (:require-macros [record]))
(:require [record]))
(prn #record.Foo{:field 1})
% clj -M --main cljs.main --compile repro
WARNING: record is a single segment namespace at line 1 /home/joel/tmp/shadow-record-literal-repro/src/record.cljc
WARNING: repro is a single segment namespace at line 1 /home/joel/tmp/shadow-record-literal-repro/src/repro.cljc
Unexpected error (ClassNotFoundException) compiling at (REPL:1).
record.Foo
Full report at:
/tmp/clojure-824673515660939121.edngotta have some meetings now, will get back to this later, thanks
#?(:cljs (:require-macros [record])) that isn't needed in ns repro since record already did it
yeah I was trying everything
various permutations of requires
well the result is what I would have expected, so not sure what special magic cljs-test-runner is doing to make it work
its different from clojure given how cljs works
I have a hypothesis that it might actually be doing a clj-side :require of malli.core before invoking the cljs compiler
but yeah .. without fail ... record literals in code = headache