Skip to content

Commit

Permalink
Support keyword path element and improved keyword annotation.
Browse files Browse the repository at this point in the history
When dispatching on strings in multimethods, it's common to use
`keyword` to instead dispatch on the equivalent keyword.

  (ann M (U '{:op '"left" :left Int}
            '{:op '"right" :right Bool}))

  (ann f [M -> Any])
  (defmulti f (fn [m :- M] (keyword (:op m))))
  (defmethod f :left [{:keys [left]}] (inc left))
  (defmethod f :right [{:keys [right]}] (not right))

Adding a path element to `keyword` allows us to infer that if the
dispatch value is :left, then the dispatch function must have returned
either "left", 'left or :left. This is sufficient to infer the argument
is a '{:op '"left" :left Int}.

Other information is also encoded in `keyword`'s annotation via propositions,
like if the return value is truthy, then the input must be (U Str Kw Sym).
  • Loading branch information
frenchy64 committed Jun 24, 2015
1 parent b173d98 commit b60ebf3
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
- Fix CTYP-214
- support quoted type syntax with string literals
- '"a" == (Val "a")
- `clojure.core/keyword` annotation is now more permissive.
- add `keyword` path element
- see `keyword-pe-test` for new idioms

# 0.3.0-alpha5 - 2 June 2015

Expand Down
17 changes: 15 additions & 2 deletions module-check/src/main/clojure/clojure/core/typed/base_env.clj
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,21 @@ clojure.core/symbol
[(U nil String) String -> Symbol])

clojure.core/keyword
(IFn [(U Keyword Symbol String) -> Keyword]
[String String -> Keyword])
(IFn [(U Keyword Symbol String) -> Keyword
:object {:id 0 :path [Keyword]}
:filters {:then tt
:else ff}]
[nil -> nil
:object {:id 0 :path [Keyword]}
:filters {:then ff
:else tt}]
[Any -> (U nil Keyword)
:object {:id 0 :path [Keyword]}
:filters {:then (is (U Keyword Symbol String) 0)
:else (! (U Keyword Symbol String) 0)}]
[String String -> Keyword
:filters {:then tt
:else ff}])

clojure.core/find-keyword
(IFn [(U Keyword Symbol String) -> (Option Keyword)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
(clojure.core.typed.filter_rep NoFilter TopFilter BotFilter TypeFilter NotTypeFilter
ImpFilter AndFilter OrFilter FilterSet)
(clojure.core.typed.object_rep NoObject EmptyObject Path)
(clojure.core.typed.path_rep KeyPE KeysPE ValsPE ClassPE NthPE CountPE)))
(clojure.core.typed.path_rep KeyPE KeysPE ValsPE ClassPE NthPE CountPE KeywordPE)))

(add-default-fold-case NotType
(fn [ty _]
Expand Down Expand Up @@ -337,6 +337,7 @@
(add-default-fold-case ClassPE ret-first)
(add-default-fold-case NthPE ret-first)
(add-default-fold-case CountPE ret-first)
(add-default-fold-case KeywordPE ret-first)

;TCResult

Expand Down
3 changes: 2 additions & 1 deletion module-check/src/main/clojure/clojure/core/typed/frees.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
(clojure.core.typed.filter_rep FilterSet TypeFilter NotTypeFilter ImpFilter
AndFilter OrFilter TopFilter BotFilter)
(clojure.core.typed.object_rep Path EmptyObject NoObject)
(clojure.core.typed.path_rep NthPE NextPE ClassPE CountPE KeyPE KeysPE ValsPE)))
(clojure.core.typed.path_rep NthPE NextPE ClassPE CountPE KeyPE KeysPE ValsPE KeywordPE)))

(alter-meta! *ns* assoc :skip-wiki true
:core.typed {:collect-only true})
Expand Down Expand Up @@ -179,6 +179,7 @@
(add-frees-method [::any-var KeyPE] [t] {})
(add-frees-method [::any-var KeysPE] [t] {})
(add-frees-method [::any-var ValsPE] [t] {})
(add-frees-method [::any-var KeywordPE] [t] {})


(add-frees-method [::frees F]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
(clojure.core.typed.filter_rep TopFilter BotFilter TypeFilter NotTypeFilter AndFilter OrFilter
ImpFilter NoFilter)
(clojure.core.typed.object_rep NoObject EmptyObject Path)
(clojure.core.typed.path_rep KeyPE CountPE ClassPE KeysPE ValsPE NthPE)
(clojure.core.typed.path_rep KeyPE CountPE ClassPE KeysPE ValsPE NthPE KeywordPE)
(clojure.lang Cons IPersistentList Symbol IPersistentVector)))

(alter-meta! *ns* assoc :skip-wiki true)
Expand Down Expand Up @@ -1103,6 +1103,8 @@
(err/int-error "Wrong arguments to Nth"))
(pthrep/NthPE-maker idx))

(defmethod parse-path-elem 'Keyword [_] (pthrep/KeywordPE-maker))

(defn- parse-kw-map [m]
{:post [((con/hash-c? r/Value? r/Type?) %)]}
(into {} (for [[k v] m]
Expand Down Expand Up @@ -1715,6 +1717,7 @@
(defmethod unparse-path-elem NthPE [t] (list 'Nth (:idx t)))
(defmethod unparse-path-elem KeysPE [t] 'Keys)
(defmethod unparse-path-elem ValsPE [t] 'Vals)
(defmethod unparse-path-elem KeywordPE [t] 'Keyword)

; Filters

Expand Down
6 changes: 6 additions & 0 deletions module-check/src/main/clojure/clojure/core/typed/path_rep.clj
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,19 @@
"Calling clojure.core/vals"
[])

(t/ann-record KeywordPE [])
(u/def-object KeywordPE []
"Calling clojure.core/keyword with a single argument."
[])

(declare-path-elem NthPE)
(declare-path-elem NextPE)
(declare-path-elem ClassPE)
(declare-path-elem CountPE)
(declare-path-elem KeyPE)
(declare-path-elem KeysPE)
(declare-path-elem ValsPE)
(declare-path-elem KeywordPE)

(def path-elems? (every-pred (some-fn nil? seq)
(con/every-c? PathElem?)))
13 changes: 12 additions & 1 deletion module-check/src/main/clojure/clojure/core/typed/path_type.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
[clojure.core.typed.contract-utils :as con]
[clojure.core.typed :as t]
#_[clojure.core.typed.debug :refer [dbg]]
[clojure.core.typed.errors :as err]))
[clojure.core.typed.errors :as err])
(:import (clojure.lang Keyword)))

(t/tc-ignore
(alter-meta! *ns* assoc :skip-wiki true)
Expand Down Expand Up @@ -88,5 +89,15 @@
r/-any)
(next ps)))

(and (pe/KeywordPE? (first ps))
(r/Value? t))
(let [{:keys [val]} t]
(path-type
(cond
;; feeding back into `keyword` gives us exactly what we want.
((some-fn symbol? string? keyword? nil? number?) val) (r/-val (keyword val))
:else (c/Un r/-nil (c/RClass-of Keyword)))
(next ps)))

:else (err/int-error (str "Bad call to path-type: " (pr-str t) ", " (pr-str ps)))
))))
39 changes: 36 additions & 3 deletions module-check/src/main/clojure/clojure/core/typed/update.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
[clojure.core.typed.profiling :as p :refer [defn]]
[clojure.set :as set]
[clojure.core.typed.remove :as remove])
(:import (clojure.lang IPersistentMap)))
(:import (clojure.lang IPersistentMap Keyword)))

;[(Seqable Filter) Filter -> Filter]
(defn resolve* [atoms prop]
Expand Down Expand Up @@ -348,8 +348,41 @@
; can we do anything for a NotTypeFilter?
t))


:else (err/int-error (str "update along ill-typed path " (pr-str (prs/unparse-type t)) " " (with-out-str (pr lo))))))))
(pe/KeywordPE? (first lo))
;; t is the old type, eg. (Val "my-key"). Can also be any type.
;; ft is the new type, eg. (Val :my-key). Can also be in (U nil Kw).
;; Approach:
;; - take the new type and un-keywordify it, then use that to update the old type.
(update* t
(cond
(r/Value? ft) (let [{:keys [val]} ft]
(cond
(keyword? val) (let [kstr (str (when (namespace val)
(str (namespace val) "/"))
(name val))]
(c/Un (r/-val kstr)
(r/-val (symbol kstr))
(r/-val val)))
(nil? val) r/-any
:else
(err/int-error (str "update Keyword path Value type that is neither keyword or nil: "
(pr-str (prs/unparse-type ft)) " " (mapv prs/unparse-path-elem lo)))))

;; if the new type is a keyword, old type must be a (U Str Sym Kw).
(sub/subtype? ft (c/RClass-of Keyword))
(c/Un (c/RClass-of Keyword)
(c/RClass-of String)
(c/RClass-of clojure.lang.Symbol))

;; could be anything
(sub/subtype? ft (c/Un r/-nil (c/RClass-of Keyword)))
r/-any

:else (err/int-error (str "update Keyword path Value type that is neither keyword or nil: "
(pr-str (prs/unparse-type ft)) " " (mapv prs/unparse-path-elem lo))))
pos? (next lo))

:else (err/int-error (str "update along ill-typed path " (pr-str (prs/unparse-type t)) " " (mapv prs/unparse-path-elem lo)))))))

(defn update [t lo]
{:pre [((some-fn fl/TypeFilter? fl/NotTypeFilter?) lo)]
Expand Down
69 changes: 69 additions & 0 deletions module-check/src/test/clojure/clojure/core/typed/test/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4820,6 +4820,75 @@
(is-tc-err "a" '"b")
(is-tc-e "a" (Val "a")))

(deftest keyword-pe-test
;; with keywords
(is-tc-e (do
(defalias M (U '{:op ':plus
:plus Int}
'{:op ':minus
:minus Int}))
(let [m :- M, {:op :plus
:plus 1}]
(if (-> m :op #{:plus})
(inc (:plus m))
(dec (:minus m))))))
;; with strings, via keyword
(is-tc-e (do
(defalias M (U '{:op '"plus"
:plus Int}
'{:op '"minus"
:minus Int}))
(let [m :- M, {:op "plus"
:plus 1}]
(if (-> m :op keyword #{:plus})
(inc (:plus m))
(dec (:minus m))))))
;; defmulti dispatch
(is-tc-e (do
(defalias M (U '{:op '"plus"
:plus Int}
'{:op '"minus"
:minus Int}))
(ann f [M -> Int])
(defmulti f (fn [m :- M] (-> m :op keyword)))
(defmethod f :plus [m] (inc (:plus m)))
(defmethod f :minus [m] (inc (:minus m)))))
;; polymorphic setting
(is-tc-e (map keyword '[a b c]))
;; with symbols, via keyword
(is-tc-e (do
(defalias M (U '{:op 'plus
:plus Int}
'{:op 'minus
:minus Int}))
(let [m :- M, {:op 'plus
:plus 1}]
(if (-> m :op keyword #{:plus})
(inc (:plus m))
(dec (:minus m))))))
;; with keywords and nil, via keyword
(is-tc-e (do
(defalias M (U '{:op ':plus
:plus Int}
'{:op nil
:minus Int}))
(let [m :- M, {:op :plus
:plus 1}]
(if (-> m :op keyword #{:plus})
(inc (:plus m))
(dec (:minus m))))))
(is-tc-e (do
(defalias M (U '{:op Num
:plus Int}
'{:op Str
:minus Int}))
(let [m :- M, {:op 1
:plus 1}]
(if (-> m :op keyword not)
(inc (:plus m))
(dec (:minus m))))))
)

; (is-tc-e
; (let [f (fn [{:keys [a] :as m} :- '{:a (U nil Num)}] :- '{:a Num}
; {:pre [(number? a)]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
Count {:op :CountPE}
Keys {:op :KeysPE}
Vals {:op :ValsPE}
Keyword {:op :KeywordPE}
(let [m (when (seq? syn)
(let [[f & args] syn]
(case f
Expand Down

0 comments on commit b60ebf3

Please sign in to comment.