[spec] anonymous predicate in s/merge always fails

Description

user=> (require '[clojure.spec.alpha :as s])
nil
user=> (s/def ::a int?)
:user/a
user=> (s/def ::b int?)
:user/b
user=> (s/def ::c (s/merge (s/keys :req [::a ::b]) #(< (::a %) (::b %))))
:user/c
user=> (s/explain-data ::c {::a 0 ::b 1})
#:clojure.spec.alpha{roblems ({ath [], red (clojure.core/fn [%] (clojure.core/< (:user/a %) (:user/b %))), :val #:user{:a 0, :b 1}, :via [:user/c], :in []}), :spec :user/c, :value #:user{:a 0, :b 1}}
user=> ;; ^^ not expected
user=> (s/explain-data ::c {::a 1 ::b 0})
#:clojure.spec.alpha{roblems ({ath [], red (clojure.core/fn [%] (clojure.core/< (:user/a %) (:user/b %))), :val #:user{:a 1, :b 0}, :via [:user/c], :in []}), :spec :user/c, :value #:user{:a 1, :b 0}}
user=> ;; ^^ expected
user=> (s/def ::a<b #(< (::a %) (::b %)))
:user/a<b
user=> (s/def ::d (s/merge (s/keys :req [::a ::b]) ::a<b))
:user/d
user=> (s/explain-data ::d {::a 0 ::b 1})
nil
user=> (s/explain-data ::d {::a 1 ::b 0})
#:clojure.spec.alpha{roblems ({ath [], red (clojure.core/fn [%] (clojure.core/< (:user/a %) (:user/b %))), :val #:user{:a 1, :b 0}, :via [:user/d :user/a<b], :in []}), :spec :user/d, :value #:user{:a 1, :b 0}}

Environment

None

Activity

Show:
David Chelimsky
February 1, 2018, 11:15 PM

It does work if you wrap the predicate in {s/spec}:

jcr
July 9, 2018, 11:54 PM

I'm unable to reproduce any of this.

In the issue description, the predicate #(< (::a %) (::b %)) require a to be less than b, so it is expected that {::a 1 ::b 0} doesn't match the spec.

In the first comment, the predicate a<b is invalid, since it accepts two numbers instead of a single map.

The following snippet works as expected too (there's no difference between anonymous pred, named pred and a pred wrapped in s/spec):

I believe the issue can be closed.

David Chelimsky
July 10, 2018, 1:27 AM

When I updated the example after Alex's comment, I put the message in the wrong place. I just updated it again with the messages correctly aligning w/ the output. jcr, I do see that your examples work, but mine still do not. So this is not quite as general as all anonymous predicates, but there is still surprising behavior.

That said, s/merge docs say it supports keys specs, and does not claim to support any other type of predicate, so if you want to close it on those grounds, have at it.

jcr
July 10, 2018, 3:41 AM

Ah, now I see what you mean. Sorry for jumping to conclusions.

So the actual problem here is that s/explain inconsistently reports inputs that are valid according to s/conform when s/merge is used with a predicate not wrapped in a s/spec call. Here's the minimal example I've managed to come up with:

I would suggest updating the title and the description accordingly, if you don't mind?

jcr
July 10, 2018, 4:25 AM

As far as I can tell, the problem is that merge-spec-impl simply calls explain-1 with the given preds, and explain-1 always returns problems if (spec? pred) is false; merge-spec-impl should probably call specize on its preds, similar to how it's done in the tuple-impl:

Assignee

Unassigned

Reporter

David Chelimsky

Labels

Approval

None

Patch

None

Affects versions

Priority

Major
Configure