Clojure core.typed annotation for apply inside a 3rd-party macro

86 views Asked by At

I'm using slingshot's throw+ macro to raise an exception that looks like:

(throw+ {:type ::urlparse})

The type checker doesn't like it:

Type Error (stream2es/http.clj:79:17) Bad arguments to apply:

Target:     [String t/Any * -> String]

Arguments:  (PersistentList String)
in: (clojure.core/apply clojure.core/format (clojure.core/list "throw+: %s" (clojure.core/pr-str %)))


Type Checker: Found 1 error

The macro in slingshot looks like:

(defmacro throw+
  ([object]
     `(throw+ ~object "throw+: %s" (pr-str ~'%)))
  ([object message]
     `(throw+ ~object "%s" ~message))
  ([object fmt arg & args]
     `(let [environment# (s/environment)
            ~'% ~object
            message# (apply format (list ~fmt ~arg ~@args))
            stack-trace# (s/stack-trace)]
        (s/throw-context ~'% message# stack-trace# environment#)))
  ([]
     `(s/rethrow)))

I've tried various ann ^:no-check forms on apply and format and none works. Since it's a macro, I'm assuming I can't annotate it since it replaces the code that's there. But I also can't rewrite the code in the macro like was suggested in this other answer, because it's in a library. How do I gradually type in this case?

1

There are 1 answers

2
Ambrose On

If you’re not able to rewrite the implementation of throw+, I suggest a wrapper macro like this.

(defmacro typed-throw+ [object]
  `(let [o# ~object]
     (t/tc-ignore
       (throw+ o#))
     (throw (Exception.)))) ; unreachable
;; other arities are an exercise ..

This way, you still type check the argument, and core.typed still thinks throw+ always throws an exception — it doesn't really know that, but the final throw clause allows core.typed to give the entire expression type Nothing.

The real answer should be we can improve apply to know that applying a non-empty list will satisfy at least one argument, however this answer should work today.