Skip to content

Commit 8267954

Browse files
committed
address #5 by extending how catch clauses work
in addition to trying to catch data, slingshot will now try to catch the wrapped exception as well. this should be less confusing for new users and make migration to slingshot easier. Signed-off-by: Sean Corfield <[email protected]>
1 parent ca542e2 commit 8267954

File tree

4 files changed

+102
-13
lines changed

4 files changed

+102
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
* v0.13.next in progress
44
* address [clj-commons/slingshot#8](https://github.com/clj-commons/slingshot/issues/8) by removing outdated status note.
5+
* address [clj-commons/slingshot#5](https://github.com/clj-commons/slingshot/issues/5) by attempting to catch the wrapper (exception) type if no catch clause matches the wrapped object type.
56
* address [clj-commons/slingshot#4](https://github.com/clj-commons/slingshot/issues/4) by merging stacktrace changes from [a20748f](https://github.com/clj-commons/slingshot/commit/a20748fd2d6d4a9020d296a3798e82f136e2bfe2) by [@scgilardi](https://github.com/scgilardi).
67
* address [clj-commons/slingshot#3](https://github.com/clj-commons/slingshot/issues/3) by merging `and` optimizations from original PR [#55](https://github.com/scgilardi/slingshot/pull/55) by [@jbouwman](https://github.com/jbouwman).
78
* address [scgilardo/slingshot#53](https://github.com/scgilardi/slingshot/issues/53) by adding examples of `else` and `finally`.
9+
* update dev/test/build deps
810

911
* v0.13.0 -- 2025-09-20
1012
- add clj-kondo config export

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,41 @@ caught {:value 5} {:object {:value 5}, :message important message, :cause nil,
129129
:throwable #error {...}}
130130
```
131131

132+
The above example shows that if you `catch Object`, you can catch non-Throwable
133+
data (as well as wrapped exceptions) and access the full throw context.
134+
135+
You can also catch based on the wrapper:
136+
137+
```clojure
138+
user=> (defn foo [x]
139+
#_=> (throw (ex-info "important message" {:value x})))
140+
#_=>
141+
#_=> (defn -main []
142+
#_=> (try+
143+
#_=> (foo 5)
144+
#_=> (catch Exception e
145+
#_=> (println "caught" e &throw-context))))
146+
#'user/foo
147+
#'user/-main
148+
user=> (-main)
149+
caught #error {
150+
:cause important message
151+
:data {:value 5}
152+
:via
153+
[{:type clojure.lang.ExceptionInfo
154+
:message important message
155+
:data {:value 5}
156+
:at [...]}]
157+
:trace
158+
[[...]]}
159+
{:object {:value 5}, :message important message, :cause nil,
160+
:stack-trace #object[[Ljava.lang.StackTraceElement...],
161+
:wrapper #error {
162+
:cause important message
163+
...},
164+
:throwable #error {...}}
165+
```
166+
132167
Between being thrown and caught, a wrapper may be further wrapped by
133168
other Exceptions (e.g., instances of `RuntimeException` or
134169
`java.util.concurrent.ExecutionException`). `try+` sees through all

src/clj_commons/slingshot/support.clj

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -153,28 +153,33 @@
153153
`(~selector ~'%))]
154154
`(let [~'% (:object ~'&throw-context)]
155155
~(or (key-values) (selector-form) (predicate)))))
156-
(cond-expression [binding-form expressions]
157-
`(let [~binding-form (:object ~'&throw-context)]
156+
(cond-expression [k binding-form expressions]
157+
`(let [~binding-form (~k ~'&throw-context)]
158158
~@expressions))
159159
(transform [[_ selector binding-form & expressions]]
160160
(if-let [class-selector (class-selector? selector)]
161161
[`(instance? ~class-selector (:object ~'&throw-context))
162-
(cond-expression (with-meta binding-form {:tag selector}) expressions)]
163-
[(cond-test selector) (cond-expression binding-form expressions)]))]
162+
(cond-expression :object (with-meta binding-form {:tag selector}) expressions)]
163+
[(cond-test selector) (cond-expression :object binding-form expressions)]))
164+
(transform-wrapper [[_ selector binding-form & expressions]]
165+
(when-let [class-selector (class-selector? selector)]
166+
[`(instance? ~class-selector (:wrapper ~'&throw-context))
167+
(cond-expression :wrapper (with-meta binding-form {:tag selector}) expressions)]))]
164168
(list
165169
`(catch Throwable ~'&throw-context
166170
(reset! ~threw?-sym true)
167171
(let [~'&throw-context (-> ~'&throw-context get-context *catch-hook*)]
168172
(cond
169-
(contains? ~'&throw-context :catch-hook-return)
170-
(:catch-hook-return ~'&throw-context)
171-
(contains? ~'&throw-context :catch-hook-throw)
172-
(~throw-sym (:catch-hook-throw ~'&throw-context))
173-
(contains? ~'&throw-context :catch-hook-rethrow)
174-
(~throw-sym)
175-
~@(mapcat transform catch-clauses)
176-
:else
177-
(~throw-sym)))))))
173+
(contains? ~'&throw-context :catch-hook-return)
174+
(:catch-hook-return ~'&throw-context)
175+
(contains? ~'&throw-context :catch-hook-throw)
176+
(~throw-sym (:catch-hook-throw ~'&throw-context))
177+
(contains? ~'&throw-context :catch-hook-rethrow)
178+
(~throw-sym)
179+
~@(mapcat transform catch-clauses)
180+
~@(mapcat transform-wrapper catch-clauses)
181+
:else
182+
(~throw-sym)))))))
178183

179184
(defn gen-finally
180185
"Returns either nil or a list containing a finally clause for a try

test/clj_commons/slingshot_test.clj

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,3 +518,50 @@
518518
(is (= result23 [exp msg]))
519519
(is (= result24 [exp fmt-msg]))
520520
(is (= result25 [exp fmt2-msg]))))))
521+
522+
(deftest test-catch-exception
523+
(testing "throw+ Exception"
524+
(let [caught (atom false)]
525+
(try+
526+
(throw+ (Exception. "test-exception"))
527+
(catch Exception e
528+
(reset! caught true)
529+
(is (= "test-exception" (.getMessage e)))))
530+
(is @caught "exception was not caught?"))
531+
(let [caught (atom false)]
532+
(try+
533+
(throw+ (Exception. "test-exception"))
534+
(catch Object e
535+
(reset! caught true)
536+
(is (= "test-exception" (.getMessage e)))))
537+
(is @caught "exception was not caught?")))
538+
(testing "throw Exception"
539+
(let [caught (atom false)]
540+
(try+
541+
(throw (Exception. "test-exception"))
542+
(catch Exception e
543+
(reset! caught true)
544+
(is (= "test-exception" (.getMessage e)))))
545+
(is @caught "exception was not caught?"))
546+
(let [caught (atom false)]
547+
(try+
548+
(throw (Exception. "test-exception"))
549+
(catch Object e
550+
(reset! caught true)
551+
(is (= "test-exception" (.getMessage e)))))
552+
(is @caught "exception was not caught?")))
553+
(testing "throw data"
554+
(let [caught (atom false)]
555+
(try+
556+
(throw+ {:msg "test-exception"})
557+
(catch Exception e
558+
(reset! caught true)
559+
(is (= "test-exception" (:msg (ex-data e))))))
560+
(is @caught "exception was not caught?"))
561+
(let [caught (atom false)]
562+
(try+
563+
(throw+ {:msg "test-exception"})
564+
(catch Object e
565+
(reset! caught true)
566+
(is (= "test-exception" (:msg e)))))
567+
(is @caught "exception was not caught?"))))

0 commit comments

Comments
 (0)