malli

Samuel Ludwig 2025-05-07T15:29:37.828129Z

any pointers on how to pin down the source of a such-that-failure exception when I run (mi/check)?

1. Unhandled clojure.lang.ExceptionInfo
   :malli.generator/such-that-failure
   {:type :malli.generator/such-that-failure, :message :malli.generator/such-that-failure, :data {:pred #function[malli.impl.util/f--11264--11265/fn--11271], :gen {:gen #function[clojure.test.check.generators/gen-bind/fn--5957]}, :max-tries 100, :schema malli.core$_and_schema$reify$reify__14161@7f7b3491}}
I don't see any of my namespaces/files in the stack-trace (aside from the start where i eval'd (mi/check))

✅ 1
2025-05-07T15:40:38.490579Z

(-> ex ex-data :schema m/form)

2025-05-07T15:42:57.648149Z

It will probably be an [:and S T] schema. That generator works by first generating S and then calling (such-that (m/validator T)) on the result.

Samuel Ludwig 2025-05-07T15:45:04.045889Z

ahh, *`(-> ex ex-data :data :schema)` and yep, looks like its an [:and map? empty?]

2025-05-07T15:45:18.053819Z

Try swapping the conjuncts.

2025-05-07T15:46:12.510779Z

This is a known weakness of the :and generator. https://github.com/metosin/malli?tab=readme-ov-file#and-generation

Samuel Ludwig 2025-05-07T15:47:36.955929Z

hmm, it looks like it just doesn't like empty?

Samuel Ludwig 2025-05-07T15:48:07.924579Z

(based on the new error, where the above just outputs "`empty?`")

2025-05-07T15:48:26.322309Z

Hmm we should inherit that generator from spec.

2025-05-07T15:49:22.506519Z

can you (mg/sample empty?) ?

Samuel Ludwig 2025-05-07T15:50:08.624609Z

ah, no-generator... so no

2025-05-07T15:50:37.274939Z

huh maybe I'm thinking of empty?

2025-05-07T15:50:40.390979Z

🙂

2025-05-07T15:52:07.020999Z

In a pinch you could use [:map {:min 0 :max 0} :any :any] or [:= nil {}]

2025-05-07T15:52:52.975269Z

(The nil syntax is explained https://github.com/metosin/malli?tab=readme-ov-file#enumeration-schemas)

Samuel Ludwig 2025-05-07T15:53:09.536199Z

ah i'll try the latter, can confirm it did indeed complain with [:= {}] when i previously tried 😅

😁 1
2025-05-07T15:54:47.602729Z

That's probably a potential error message improvement to m/check-children! .

2025-05-07T15:56:56.316369Z

Hmm no I remembered correctly, spec provides an empty? generator https://github.com/clojure/spec.alpha/blob/dbb07276310d156922f9a340ccaae410732b6c91/src/main/clojure/clojure/spec/gen/alpha.clj#L184

2025-05-07T15:57:06.939729Z

I'll take a look.

2025-05-07T15:58:09.458379Z

Works on the playground https://malli.io/?value=undefined&schema=empty%3F

2025-05-07T15:59:08.013449Z

Could you send the stacktrace from your second attempt?

Samuel Ludwig 2025-05-07T16:05:54.506879Z

interesting, will do

Samuel Ludwig 2025-05-07T16:06:30.658919Z

and yea im now encountering different exceptions with some harder-to-trace provenance; but i'll get that stacktrace first

Samuel Ludwig 2025-05-07T16:08:50.368619Z

this is the ST I get from running (mg/sample empty?)

1. Unhandled clojure.lang.ExceptionInfo
   :malli.generator/no-generator
   {:type :malli.generator/no-generator, :message :malli.generator/no-generator, :data {:options nil, :schema malli.core$_simple_schema$reify$reify__14123@6f6b781a}}
                 core.cljc:  157  malli.core$_exception/invokeStatic
                 core.cljc:  157  malli.core$_exception/invoke
                   dev.clj:   18  malli.dev/-capture-fail!/fn/-fail!
                  AFn.java:  156  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
            AFunction.java:   33  clojure.lang.AFunction$1/doInvoke
               RestFn.java:  424  clojure.lang.RestFn/invoke
            generator.cljc:  474  malli.generator$_create/invokeStatic
            generator.cljc:  467  malli.generator$_create/invoke
            generator.cljc:  490  malli.generator$generator$fn__19212/invoke
                 core.cljc:  303  malli.core$_cached/invokeStatic
                 core.cljc:  300  malli.core$_cached/invoke
            generator.cljc:  490  malli.generator$generator/invokeStatic
            generator.cljc:  483  malli.generator$generator/invoke
            generator.cljc:  503  malli.generator$sample/invokeStatic
            generator.cljc:  499  malli.generator$sample/invoke
            generator.cljc:  501  malli.generator$sample/invokeStatic
            generator.cljc:  499  malli.generator$sample/invoke
                      REPL:  169  <MYNAMESPACE>-request/eval93017
                      REPL:  169  <MYNAMESPACE>-request/eval93017
             Compiler.java: 8035  clojure.lang.Compiler/eval
    interruptible_eval.clj:  106  nrepl.middleware.interruptible-eval/evaluator/run/fn
    interruptible_eval.clj:  101  nrepl.middleware.interruptible-eval/evaluator/run
               session.clj:  230  nrepl.middleware.session/session-exec/session-loop
        SessionThread.java:   21  nrepl.SessionThread/run

2025-05-07T16:11:10.166619Z

what does (prn (m/validator empty?)) return?

2025-05-07T16:11:44.321749Z

or (identical? empty? (m/validator empty?))

Samuel Ludwig 2025-05-07T16:12:45.927759Z

nil for the first, false for the second

2025-05-07T16:13:44.145179Z

do you have a custom global registry?

2025-05-07T16:16:10.094179Z

oh!

❗ 1
Samuel Ludwig 2025-05-07T16:16:14.457409Z

i don't, fanciest thing i have is a few schemas defined with the time-schemas merged into the base, but they are one off (def MySchema [:time/date-time ...] {:registry (merge defaults+time-schemas)}) definitions, not with an altered global

2025-05-07T16:16:55.815909Z

That must be the function you see?

2025-05-07T16:17:03.398039Z

from (m/validator empty?)

2025-05-07T16:17:22.421539Z

malli.core$predicate-schemas$fn.....

2025-05-07T16:17:23.601809Z

?

Samuel Ludwig 2025-05-07T16:18:33.716909Z

indeed! -safe-empty?

2025-05-07T16:18:46.332729Z

aha! I have no idea how this works in cljs 🙂

Samuel Ludwig 2025-05-07T16:19:15.418279Z

im not on cljs though?

Samuel Ludwig 2025-05-07T16:19:40.056919Z

(nor babashka, normal boring clojure)

2025-05-07T16:19:55.846249Z

Sorry, I'll catch you up with my free associative thinking xD

Samuel Ludwig 2025-05-07T16:20:02.785319Z

ah i appreciate you!

2025-05-07T16:20:20.806079Z

The cljs malli playground clearly "sampled" empty? somehow.

1
2025-05-07T16:20:53.918179Z

And the way the generator for empty? works, is that it uses the pointer identity of (m/validator empty?) to lookup the generator from spec https://github.com/metosin/malli/blob/05951278522f6b2cd8669ea1d42e3934edb10db9/src/malli/generator.cljc#L374

2025-05-07T16:21:06.856439Z

So this generator should never work

2025-05-07T16:21:15.187319Z

(this is a bug)

Samuel Ludwig 2025-05-07T16:23:07.044499Z

!!, i appreciate this! ofc the [:= nil {}] schema will work just fine for now

2025-05-07T16:23:27.589059Z

Thanks for your help!

Samuel Ludwig 2025-05-07T16:24:34.657669Z

thank you! if its any consolation, i'll probably have another question regarding parsing another stack-trace, ill post that as a separate thread though 😄

👍 1
2025-05-07T16:25:30.889779Z

reported https://github.com/metosin/malli/issues/1194

Samuel Ludwig 2025-05-07T16:40:02.992579Z

determining provenance of (mi/check) exceptions pt 2: NPE-edition my most recent runs of (mi/check) are producing a NullPointerException

1. Unhandled java.lang.NullPointerException
   Cannot invoke "clojure.lang.IFn.invoke(Object, Object)" because "this.f" is
   null
am I able to run check for only a specific namespace to try and narrow down what function/schema is causing it displeasure? (full stacktrace in thread)

✅ 1
Samuel Ludwig 2025-05-07T16:41:11.849329Z

1. Unhandled java.lang.NullPointerException
   Cannot invoke "clojure.lang.IFn.invoke(Object, Object)" because "this.f" is
   null
                  core.clj: 2777  clojure.core/map/fn
              LazySeq.java:   50  clojure.lang.LazySeq/force
              LazySeq.java:   89  clojure.lang.LazySeq/realize
              LazySeq.java:  106  clojure.lang.LazySeq/seq
                   RT.java:  555  clojure.lang.RT/seq
                  core.clj:  139  clojure.core/seq
                  core.clj:  139  clojure.core/seq
                 core.cljc: 1309  malli.core$_collection_schema$reify$reify__14471$fn__14480/invoke
                 core.cljc:  774  malli.core$_and_schema$reify$reify__14161$explain__14168$fn__14169/invoke
     PersistentVector.java:  418  clojure.lang.PersistentVector/reduce
                  core.clj: 6964  clojure.core/reduce
                  core.clj: 6947  clojure.core/reduce
                 core.cljc:  774  malli.core$_and_schema$reify$reify__14161$explain__14168/invoke
                 core.cljc: 2345  malli.core$explainer$explainer__14981/invoke
                 core.cljc: 2356  malli.core$explain/invokeStatic
                 core.cljc: 2350  malli.core$explain/invoke
                 core.cljc: 2354  malli.core$explain/invokeStatic
                 core.cljc: 2350  malli.core$explain/invoke
            generator.cljc:  531  malli.generator$function_checker$check__19232$fn__19237/invoke
                 core.cljc: 1872  malli.core$__EQ__GT$reify$reify__14778$explain__14785/invoke
                 core.cljc: 2345  malli.core$explainer$explainer__14981/invoke
                 core.cljc: 2356  malli.core$explain/invokeStatic
                 core.cljc: 2350  malli.core$explain/invoke
                 core.cljc: 2354  malli.core$explain/invokeStatic
                 core.cljc: 2350  malli.core$explain/invoke
            generator.cljc:  549  malli.generator$check/invokeStatic
            generator.cljc:  545  malli.generator$check/invoke
            generator.cljc:  546  malli.generator$check/invokeStatic
            generator.cljc:  545  malli.generator$check/invoke
            instrument.clj:  124  malli.instrument/check/fn
            instrument.clj:   32  malli.instrument/-strument!/iter/fn/iter/fn
              LazySeq.java:   50  clojure.lang.LazySeq/force
              LazySeq.java:   89  clojure.lang.LazySeq/realize
              LazySeq.java:  106  clojure.lang.LazySeq/seq
                   RT.java:  555  clojure.lang.RT/seq
                  core.clj:  139  clojure.core/seq
                  core.clj:  727  clojure.core/concat/fn
              LazySeq.java:   50  clojure.lang.LazySeq/force
              LazySeq.java:   89  clojure.lang.LazySeq/realize
              LazySeq.java:  106  clojure.lang.LazySeq/seq
                 Cons.java:   41  clojure.lang.Cons/next
                   RT.java:  733  clojure.lang.RT/next
                  core.clj:   64  clojure.core/next
                  core.clj: 3150  clojure.core/dorun
                  core.clj: 3156  clojure.core/doall
                  core.clj: 3156  clojure.core/doall
            instrument.clj:   18  malli.instrument/-strument!
            instrument.clj:   15  malli.instrument/-strument!
            instrument.clj:  123  malli.instrument/check
            instrument.clj:  117  malli.instrument/check
            instrument.clj:  120  malli.instrument/check
            instrument.clj:  117  malli.instrument/check
                      REPL:  167  <MYNAMESPACE>/eval93037

Samuel Ludwig 2025-05-07T16:53:26.304099Z

ah ok stepping through with the glorious #flow-storm actually brings me right to the cause, i don't know why i just assumed that the function-call would have been mangled or something so the solution here is to actually use the introspection tools available to you :^)

Samuel Ludwig 2025-05-07T16:54:05.766899Z

(though, the cause does seem a bit hidden upfront based on the check-triggered exception)

Samuel Ludwig 2025-05-07T20:09:42.286519Z

is there a way to exclude a spec'd function from mi/check? i do have some side-effectful functions that have schema's i'd like to have instrumented while in dev-mode, but not, say make a few hundred subsequent http-requests during generative testing :^)