Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 22 additions & 17 deletions src/malli/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -1940,14 +1940,16 @@
;; returns an identifier for the :ref schema in the context of its dynamic scope.
;; useful for detecting cycles.
;; copied to malli.generator
(defn- -identify-ref-schema [schema]
;; TODO mr/-schemas doesn't seem right, making defn private for now.
;; e.g., we only care about property registry entries, not schema constructors.
;; a better approach might be to accumulate a 'seen' map from name => ?schema
;; that we add to every time we deref a ref, and if we expand the same name again
;; with the same seen map, it's a cycle.
{:scope (-> schema -options -registry mr/-schemas)
:name (-ref schema)})
(defn- -identify-ref-schema
([schema] (-identify-ref-schema (-ref schema) (-options schema)))
([ref options]
;; TODO mr/-schemas doesn't seem right, making defn private for now.
;; e.g., we only care about property registry entries, not schema constructors.
;; a better approach might be to accumulate a 'seen' map from name => ?schema
;; that we add to every time we deref a ref, and if we expand the same name again
;; with the same seen map, it's a cycle.
{:scope (-> options -registry mr/-schemas)
:name ref}))

(def ^:dynamic ^:private *ref-validators* {})

Expand All @@ -1962,16 +1964,20 @@
IntoSchema
(-type [_] :ref)
(-type-properties [_] type-properties)
(-into-schema [parent properties [ref :as children] {::keys [allow-invalid-refs] :as options}]
(-into-schema [parent properties [ref :as children] {::keys [allow-invalid-refs ref-id->nested-info] :as options}]
(-check-children! :ref properties children 1 1)
(when-not (-reference? ref)
(-fail! ::invalid-ref {:ref ref}))
(let [?schema (-memoize
#(or (mr/-schema (-registry options) ref)
(when-not allow-invalid-refs
(-fail! ::invalid-ref {:type :ref, :ref ref}))))
_ (when-not lazy (?schema))
rf (-memoize #(schema (?schema) options))
(let [id (-identify-ref-schema ref options)
nested-info (get ref-id->nested-info id)
rf (or (:rf nested-info)
(let [?schema (-memoize
#(or (mr/-schema (-registry options) ref)
(when-not allow-invalid-refs
(-fail! ::invalid-ref {:type :ref, :ref ref}))))
_ (when-not lazy (?schema))
vrf (volatile! nil)]
(vreset! vrf (-memoize #(schema (?schema) (assoc-in options [::ref-id->nested-info id :rf] @vrf))))))
children (vec children)
form (delay (-simple-form parent properties children identity options))
cache (-create-cache options)
Expand All @@ -1983,8 +1989,7 @@
(-to-ast [this _] (-to-value-ast this))
Schema
(-validator [this]
(let [id (-identify-ref-schema this)
id->validator *ref-validators*]
(let [id->validator *ref-validators*]
(or (id->validator id)
(let [knot (atom nil)
rec #(@knot %)
Expand Down
16 changes: 16 additions & 0 deletions test/malli/core_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -3681,3 +3681,19 @@
"world" [:enum "maailma"])
(is (= ["hello" "maailma"]
(mapv (comp peek m/form m/deref-all) (m/children schema)))))))

(deftest recursive-explainer-test
(let [count-into-schemas (atom 0)
reg (mr/simple-registry (assoc (m/default-schemas)
::counting (m/-proxy-schema {:type ::counting
:fn (fn [p c o]
(assert (empty? c))
(swap! count-into-schemas inc)
[[] [] (m/schema :int o)])})))
ConsCell (m/schema [:schema {:registry {::cons [:maybe [:tuple ::counting [:ref ::cons]]]}} ::cons]
{:registry reg})]
(is (= @count-into-schemas 2))
(is (nil? (m/explain ConsCell [1 [2 [3 [4 nil]]]])))
(is (= @count-into-schemas 3)) ;; was 6
(is (nil? (m/explain ConsCell [1 [2 [3 [4 [1 [2 [3 [4 nil]]]]]]]])))
(is (= @count-into-schemas 3)))) ;; was 10