Fork me on GitHub
#cljs-dev
<
2018-08-20
>
dnolen15:08:13

@mfikes we definitely want type inference to flow along based on conditionals

mfikes15:08:18

@dnolen Cool. Taking that as meaning you think https://dev.clojure.org/jira/browse/CLJS-2866 is worthwhile.

mfikes17:08:39

Pretty cool (predicate-induced inferrence):

(let [xxx 'a]
  (inferred-type
    (cond
      (pos? xxx) xxx
      (boolean? xxx) xxx
      (nil? xxx) xxx
      :else "foo")))
;=> #{boolean number string clj-nil}

mfikes17:08:50

A simpler one:

(let [xxx 'a] (inferred-type (if (double? xxx) xxx :kw)))
;=> #{cljs.core/Keyword number}

mfikes17:08:38

^ What you can’t really see is that the type inference goes with the then branch, so in that first example, only the xxx on the pos? branch is tagged with number

mfikes17:08:21

Proof of this assertion:

cljs.user=> (let [xxx 'a]
  (cond
    (string? xxx) (+ xxx 3)
    (boolean? xxx) (+ xxx 3)))
WARNING: cljs.core/+, all arguments must be numbers, got [string number] instead at line 3 <cljs repl>
WARNING: cljs.core/+, all arguments must be numbers, got [boolean number] instead at line 4 <cljs repl>
nil

richiardiandrea17:08:06

the alias in core is clojure.spec.test.check but then I checked the code and I am not sure where it is used...will see if I can patch it myself

richiardiandrea18:08:25

uhm, never mind...seems like :clojure.test.check/opts is handled correctly so all good

dnolen18:08:35

@mfikes a next step would be to type tagging predicates, i.e. seqable? or instance?, satisfies? etc.

mfikes18:08:29

I have seq? covered. I wonder if seqable? implies any particular tag?

mfikes18:08:24

instance? and satisfies? don’t fit into the pattern currently handled, but it seems like a mild extension to handle those cases

dnolen18:08:02

actually for seqable?, ideally outward propagation would handle it

dnolen18:08:16

if you handle instance? and satsifies?

mfikes18:08:06

Oh, so, looking at the source for sequable?, if would appear that in the then branch of

(if (seqable? x)
  x
  :foo)
the local x should have the tag #{cljs.core/ISeqable array string}. That could fit into the pattern easily. Just a sec…

mfikes18:08:37

On the surface, this now looks right to me:

mfikes18:08:41

cljs.user=> (inferred-type (let [xxx 'x] (when (seqable? xxx) xxx)))
#{cljs.core/ISeqable array string clj-nil}

mfikes18:08:25

That was a trivial change. Got lucky. Just had to revise the predicate->tag map to add an entry (I also added one for array?)

(def ^:private predicate->tag
  '{cljs.core/nil? clj-nil
    cljs.core/boolean? boolean
    cljs.core/string? string
    cljs.core/number? number
    cljs.core/double? number
    cljs.core/integer? number
    cljs.core/zero? number
    cljs.core/pos? number
    cljs.core/neg? number
    cljs.core/seq? seq
    cljs.core/array? array
    cljs.core/seqable? #{cljs.core/ISeqable array string}})

mfikes18:08:05

This is pretty good. I’ll add the instance? and satisfies? and see how they pan out.

nate06:08:23

I’d be interested in any information you compiled. It’s been a while since I’ve done cljs in vim and I’m curious about the composition of a working configuration.

nate06:08:40

Thanks in advance for sharing.

colbydehart13:08:12

Yeah ill write up a blogpost for it.

nate15:08:20

Awesome! I’ll check it out later today.

mfikes19:08:46

Cool, satisfies? and instance? appear to be easy to do:

cljs.user=> (inferred-type (let [xxx 'x] (if (satisfies? ICounted xxx) xxx 2)))
#{number cljs.core/ICounted}
cljs.user=> (inferred-type (let [xxx 'x] (if (instance? MapEntry xxx) xxx 2)))
#{cljs.core/MapEntry number}
but I think we’d have to add specific table entries for predicates like map-entry?
cljs.user=> (inferred-type (let [xxx 'x] (if (map-entry? xxx) xxx 2)))
#{number cljs.core/Symbol}
Will add the core predicates that delegate to satisfies? and instance? (like map-entry?)

mfikes21:08:43

I dropped a WIP patch in https://dev.clojure.org/jira/browse/CLJS-2866 for the above. It would actually probably be ready, apart from a unit test failure in cljs.extend-to-native-test. But apart from that, the code is in good shape for any feedback.

mfikes22:08:16

The failure mode is interesting: In pr-writer-impl we have a cond branch that looks like

(satisfies? IPrintWithWriter obj)
        (-pr-writer obj writer opts)
and here is what failure looks like
cljs.user=> (extend-type object
  IPrintWithWriter
  (-pr-writer [obj writer _]
      (write-all writer "#object[custom-print-cljs-2812]")))
nil
cljs.user=> #js {}
repl:13
throw e__8737__auto__;
^

TypeError: obj.cljs$core$IPrintWithWriter$_pr_writer$arity$3 is not a function
at Object.cljs$core$pr_writer_impl [as pr_writer_impl] (/Users/mfikes/out/cljs/core.js:32916:12)
...
It appears that, since obj gets tagged as being of type IPrintWithWriter, the compiler appears to emit what looks like a static dispatch or somesuch to a method that doesn’t exist. Revising the cond branch to suppress this with ^any, like this, fixes it:
(satisfies? IPrintWithWriter obj)
        (-pr-writer ^any obj writer opts)
So this new tag information is provoking some sort of assumption in the compiler in this case.

mfikes22:08:28

Maybe we will have to put satisfies? inference behind a compiler flag that is off by default?