spectrum
spectrum copied to clipboard
Add special case (ann) for clojure.core/keyword
clojure.core/keyword takes a string and returns a keyword. However, in order to support certain behaviours the function returns nil whenever it gets something that is not a string, symbol or keyword, instead of crashing.
This is unfortunate for type checking, since you cannot assume keyword will return not-nil even when the type checker can prove that it's being called with a string, forcing you to pollute the codebase with nilable specs.
This can be fixed with the following ann:
(spectrum.ann/ann #'keyword
(fn [fnspec argspec]
(if (#{#'clojure.core/symbol?
#'clojure.core/keyword?
#'clojure.core/string?}
(-> argspec :ps first :pred))
(-> fnspec
(update-in [:ret :ps] pop)
(update-in [:ret :ks] pop))
fnspec)))
Would this be a good addition to ann.clj?
Yes, keyword needs an ann, and would make a good addition. If you're interested in making a PR, a few notes:
- prefer
c/first*,c/rest*over:ps.:psis an implementation detail of concrete spects, whilefirst*,rest*use protocols. - in the cases where keyword returns nil, update the fnspec
:retto(c/value nil).c/valueis a spect that says, 'I know exactly which value this fn returns'. - Make sure you handle both arities of
keyword. Look at the spec forkeywordincore_specs.clj, in particular, the 1-arity case accepts any? in the first arg, while the 2 arity requires string or nil in the first arg, and string in the second. - Add tests in
ann_test, the best way is to probably usecheck/type-of, ie
(= (c/pred-spec #'keyword?) (check/type-of '(keyword x) {:x (c/pred-spec #'string?)}))
Thanks!
I'd be glad to contribute! I'll be looking at it this weekend. Thanks for the comments!
is this ticket still valid?
spectrum.repl> (f/infer-var #'keyword)
[fn
{[cat ?t±897] [or [#'clojure.core/keyword? [value nil]]],
[cat
[or [#'clojure.core/string? [value nil]]]
[or [#'clojure.core/string? [value nil]]]]
[or [#'clojure.core/keyword? [value nil]]]}]