clojure-emacs / cider

The Clojure Interactive Development Environment that Rocks for Emacs
https://cider.mx
GNU General Public License v3.0
3.54k stars 645 forks source link

Missing exception output when evaluating a form from cljs-buffer #3223

Open witek opened 2 years ago

witek commented 2 years ago

Remove all of the placeholder text in your final report!

Expected behavior

Given a cljs form which throws an exception. When evaluating the form in a cljs-buffer, the thrown exception should be printed in the REPL-buffer the same way as if it was evaluated in the REPL-buffer.

Actual behavior

Evaluating a cljs form which throws an exception from the REPL-buffer prints the exception. Evaluating the same form from the cljs-buffer does not print the exception.

Steps to reproduce the problem

I have a deps.edn project with shadow-cljs and a Google Firebase Functions emulator as runtime. Connected to CIDER. I could reproduce the same problem with the browser as runtime.

When I evaluate (println "hello") in a cljs-buffer, I get ==> nil as output in the buffer. And I can see hello printed into the REPL-buffer.

When I evaluate (throw (js/Error. "boom")) in the cljs-buffer, I get ==> :repl/error as output in the buffer. And nothing is printed into the REPL-buffer. But when I evaluate (throw (js/Error. "boom")) in the REPL-buffer, then the error is printed into the REPL-buffer right before ==> :repl/error.

So it seams a successful evaluation from a cljs-buffer "happens" in the REPL-buffer, while a failed evaluation does not.

Environment & Version information

CIDER version information

;; CIDER 1.3.0 (Ukraine), nREPL 0.9.0
;; Clojure 1.11.1, Java 11.0.15

Emacs version

GNU Emacs 28.1.50

Operating system

Manjaro Ruah 21.3.3

shadow-cljs

thheller/shadow-cljs {:mvn/version "2.19.5"}

bbatsov commented 2 years ago

Can you post here the message exchange with nREPL? (see https://docs.cider.mx/cider/troubleshooting.html#debugging-the-communication-with-nrepl)

BenSturmfels commented 2 years ago

I'm seeing the same issue and have attached the nREPL message exchange from evaluating the following in a source code buffer:

(println "hello")
(throw (js/Error. "boom"))

nrepl-message-exchange.txt

As mentioned above, evaluating directly in the nREPL buffer shows the exception as expected, but in the source buffer it just shows :repl/exception!.

My versions:

;; CIDER 1.4.1 (Kyiv), nREPL 0.9.0
;; Clojure 1.11.1, Java 1.8.0_292
BenSturmfels commented 2 years ago

Whoops! Reading the original issue more closely, the behaviour I'm seeing is slightly different. When I evaluate (throw (js/Error. "boom")) in the cljs-buffer, I get => :repl/exception! as output in the buffer. And the the exception is printed into the REPL-buffer:

Execution error (Error) at (<cljs repl>:1).
boom

That behaviours seems reasonably appropriate to me, thought it might be handy to get more detail without having to switch to the nREPL buffer.

I'm on shadow-cljs 2.19.6.

The behaviour that I find most confusing is that when I evaluate something undeclared like (x), in the CLJS source buffer, I see => nil implying that the function successfully evaluated to nil. I also get the WARNING - :undeclared-var in the nREPL buffer, but often don't have that on-screen. I've attached the nREPL message exchange for that. Happy to raise as a separate issue if appropriate.

nrepl-message-exchange2.txt

BenSturmfels commented 2 years ago

For context, when I run (x) at a straight clojurescript REPL, I get:

$ clj -M --main cljs.main --repl --repl-env browser
ClojureScript 1.10.758
cljs.user=> (x)
WARNING: Use of undeclared Var cljs.user/x at line 1 <cljs repl>
Execution error (TypeError) at (<cljs repl>:1).
cljs.user.x is undefined

It's much clearer here that I made a mistake, rather than the function successfully returned nil.

danskarda commented 1 year ago

:repl/exception! result is provided by shadow-cljs. It does not send detailed information about exception to cider.

If you grep shadow-cljs for :repl/exception! you will get following code in shadow-cljs/src/main/shadow/cljs/devtools/server/repl_impl.clj:229

  (cond
     (= :error (:stage repl-state))
     (do (repl-stderr repl-state (str "\n" result))
         ;; FIXME: should there be an actual result? looks annoying on the streaming REPLs
         ;; this was only here for nREPL right?
         (repl-result repl-state ":repl/exception!"))

When I search for code which sets :stage to :eror in repl-state, I find repl_impl.clj:269:

                   :eval-runtime-error
                   (let [{:keys [from ex-oid]} msg]
                     (>!! to-relay
                       {:op :obj-request
                        :to from
                        :request-op :ex-str
                        :oid ex-oid})
                     (recur (assoc repl-state :stage :error)))

My interpretation is that shadow-cljs receives runtime error and asks js repl for details (obj-request). It receives the response but does not forward it to cider, instead it only writes string ":repl/exception!" which is read as a symbol by cider.

Is my observation correct? How can we fix this? /cc @thheller

thheller commented 1 year ago

I'm guessing the behavior that confuses cider is that when an error occurs it sends the error message via :err messages and then :repl/exception! as the final "result" value. I'm not sure if the other REPLs maybe just don't send a value at all? My guess is that cider drops :err messages and doesn't display them if it receives a :value?

If you look over the nrepl message dumps posted above you can clearly find the errors being sent to cider. What it does with it I do not know.

(<--
  id         "16996"
  session    "687f4192-fe5a-48d9-87f4-e453a505fbd7"
  time-stamp "2022-07-25 10:37:48.079125089"
  err        "
Execution error (Error) at (<cljs repl>:1).
boom
"
)
(<--
  id         "16996"
  session    "687f4192-fe5a-48d9-87f4-e453a505fbd7"
  time-stamp "2022-07-25 10:37:48.093663587"
  ns         "cljs.user"
  value      ":repl/exception!"
)

If cider is indeed confused by that I can just drop the :repl/exception!?

Here is the current behavior looks in Cursive, which I use and is the only thing I tested this with.

(throw (js/Error. "boom"))

Execution error (Error) at (<cljs repl>:1).
boom
=> :repl/exception!