From 25321426174495dfd8a2274e9b30743f46460a5c Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 9 Oct 2026 09:45:47 +0300 Subject: [PATCH] Compare servers with nREPL 1.8.0 nREPL 1.8.0 fixes reading past the end of stdin, which threw a ClassCastException in 1.7.0, so the Clojure profile passes stdin.eof now too. proof serve already gives the same replies as 1.8.0 for the code it understands, going by transcripts of the two. The eof-error scenario stays, as 1.7.0 is what CIDER injects, but -like clojure behaves like 1.8.0 and leaves it out. The links into nREPL's code point at the 1.8.0 release, with the lines of session.clj moved down by the code 1.8.0 added above them. --- doc/design.md | 2 +- doc/usage.md | 6 +++--- internal/checks/refs.go | 12 ++++++------ internal/serve/eval.go | 2 +- internal/serve/matrix_test.go | 4 ++-- internal/serve/scenarios.go | 2 +- profiles/clojure.toml | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/design.md b/doc/design.md index a0f09c2..4ac5b70 100644 --- a/doc/design.md +++ b/doc/design.md @@ -208,7 +208,7 @@ proof is not a Clojure implementation, so `proof serve` understands only a small piece of Clojure. That's enough for the snippets of the nREPL profile, the code CIDER and vim-fireplace send when they connect and what client tests need (output, values, errors, input, something to -interrupt), and it gives the same replies as nREPL 1.7.0 for the same +interrupt), and it gives the same replies as nREPL 1.8.0 for the same code. Code in other languages wouldn't help, as no client's tests send Erlang to a server. diff --git a/doc/usage.md b/doc/usage.md index 78653f9..ccde6be 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -223,7 +223,7 @@ recording every message on the way: ```shell $ proof proxy -listen 127.0.0.1:7888 profiles/clojure.toml -Starting Clojure (nrepl/nrepl 1.7.0)... +Starting Clojure (nrepl/nrepl 1.8.0)... Forwarding 127.0.0.1:7888 to localhost:53613. Connect your client to 127.0.0.1:7888 and press Ctrl-C when it's done. ``` @@ -238,7 +238,7 @@ client FAIL client.need-input need-input is answered with stdin in the same session need-input went unanswered (during connection 1): {id "2", session "dd88...", status ["need-input"]} why: Code reading input waits until it gets some, so an unanswered need-input leaves the eval, and the session it runs in, hanging forever. - see: nREPL hands stdin to the reader of the request's session https://github.com/nrepl/nrepl/blob/edf294a7.../src/clojure/nrepl/middleware/session.clj#L380-L388 + see: nREPL hands stdin to the reader of the request's session https://github.com/nrepl/nrepl/blob/16cc5cb0.../src/clojure/nrepl/middleware/session.clj#L386-L394 WARN client.close Sessions are closed before disconnecting a session was never closed (during connection 1): {id "1", new-session "dd88...", session "d5b1...", status ["done"]} ... @@ -373,7 +373,7 @@ hand (e.g. "this eval shows this output and this value") and run them once for every scenario. proof can't evaluate real Clojure, of course. Instead it understands just -enough of it for tests, and gives the same replies as nREPL 1.7.0 does +enough of it for tests, and gives the same replies as nREPL 1.8.0 does for the same code: | Code | What it does | diff --git a/internal/checks/refs.go b/internal/checks/refs.go index f7e556b..e5b1371 100644 --- a/internal/checks/refs.go +++ b/internal/checks/refs.go @@ -13,7 +13,7 @@ const ( calvaBase = "https://github.com/BetterThanTomorrow/calva/blob/ad00dd7e518e0d08262810673111eaeddc5c2fdd/src/nrepl/index.ts" conjureBase = "https://github.com/Olical/conjure/blob/9842bf38464df071f72546f65fa6ec44942ca7b2/fnl/conjure/client/clojure/nrepl/server.fnl" fireplaceBase = "https://github.com/tpope/vim-fireplace/blob/5e66509599de92550762cf2681338fc4cd1e71cf/autoload/" - nreplBase = "https://github.com/nrepl/nrepl/blob/edf294a7739b99549accdb6dbbc2fc83db6d9094/src/clojure/nrepl/" + nreplBase = "https://github.com/nrepl/nrepl/blob/16cc5cb0f2adae44d389ba23c864a02eaa4e71a5/src/clojure/nrepl/" specBase = "https://github.com/nrepl/spec.nrepl.org/blob/67796e34ac34f2f28c3af685fc3ab432fe5eb03f/spec.md" replyBase = "https://github.com/trptcolin/reply/blob/2b28587004aa3b5cd5d4548eb87f7674eb999b13/src/reply/eval_modes/nrepl.clj" rebelBase = "https://github.com/bhauman/rebel-readline/blob/d8573a61aad5cbbd83532e2050b6595cfb5b13bb/rebel-readline-nrepl/src/rebel_readline/nrepl/service/nrepl.clj" @@ -53,8 +53,8 @@ var ( nreplNoCode = ref("nREPL no-code reply", nreplBase+"middleware/interruptible_eval.clj#L190") nreplNsNotFound = ref("nREPL namespace-not-found reply", nreplBase+"middleware/interruptible_eval.clj#L197") nreplEvalError = ref("nREPL eval-error reply", nreplBase+"middleware/interruptible_eval.clj#L118-L123") - nreplSessionClosed = ref("nREPL close reply", nreplBase+"middleware/session.clj#L303") - nreplUnknownSess = ref("nREPL unknown-session reply", nreplBase+"middleware/session.clj#L335") + nreplSessionClosed = ref("nREPL close reply", nreplBase+"middleware/session.clj#L309") + nreplUnknownSess = ref("nREPL unknown-session reply", nreplBase+"middleware/session.clj#L341") nreplDescribe = ref("nREPL describe reply", nreplBase+"middleware.clj#L62-L66") specProtocol = ref("spec: protocol description", specBase+"#L24-L56") specDescribe = ref("spec: describe op", specBase+"#L62-L93") @@ -64,14 +64,14 @@ var ( specClose = ref("spec: close op", specBase+"#L390-L414") specStdin = ref("spec: stdin op", specBase+"#L186-L215") ciderNeedInput = ref("CIDER need-input handling", ciderBase+"nrepl-client.el#L948-L950") - nreplStdinEOF = ref("nREPL treats an empty stdin as EOF", nreplBase+"middleware/session.clj#L380-L390") + nreplStdinEOF = ref("nREPL treats an empty stdin as EOF", nreplBase+"middleware/session.clj#L386-L396") // Server code, for the client rules. nreplReplyID = ref("nREPL copies the id into replies only if there is one", nreplBase+"misc.clj#L74") nreplConnLoop = ref("nREPL closes a connection it can't read a request from", nreplBase+"server.clj#L83-L90") nreplSessions = ref("nREPL keeps sessions (and their threads) until they're closed", nreplBase+"middleware/session.clj#L20-L26") - nreplInterruptNoSes = ref("nREPL can't interrupt without a session", nreplBase+"middleware/session.clj#L271-L277") - nreplStdinSession = ref("nREPL hands stdin to the reader of the request's session", nreplBase+"middleware/session.clj#L380-L388") + nreplInterruptNoSes = ref("nREPL can't interrupt without a session", nreplBase+"middleware/session.clj#L277-L283") + nreplStdinSession = ref("nREPL hands stdin to the reader of the request's session", nreplBase+"middleware/session.clj#L386-L394") nreplLineColumn = ref("nREPL casts line and column to int", nreplBase+"middleware/interruptible_eval.clj#L42-L43") nreplEvalNs = ref("nREPL turns ns into a symbol", nreplBase+"middleware/interruptible_eval.clj#L196") bbUnknownID = ref("babashka.nrepl replies with id \"unknown\"", bbNreplBase+"impl/utils.clj#L13") diff --git a/internal/serve/eval.go b/internal/serve/eval.go index 88da94f..d91126f 100644 --- a/internal/serve/eval.go +++ b/internal/serve/eval.go @@ -170,7 +170,7 @@ func compileError(form, format string, args ...any) error { phase: compiling, form: form} } -// errorReport is what nREPL 1.7.0 sends about an exception: the text for +// errorReport is what nREPL 1.8.0 sends about an exception: the text for // err, and the classes for ex and root-ex. func errorReport(t *thrown, ns string) (text, ex, rootEx string) { class := "class " + t.ex.class diff --git a/internal/serve/matrix_test.go b/internal/serve/matrix_test.go index 1840259..4d461de 100644 --- a/internal/serve/matrix_test.go +++ b/internal/serve/matrix_test.go @@ -107,7 +107,7 @@ func TestPresetsGetTheColumnsOfTheirServers(t *testing.T) { noLanguage := map[string]check.Verdict{"eval.ns": S, "eval.unknown-ns": S, "cider.eval": S, "calva.eval": S, "conjure.eval": S} columns := map[string]map[string]check.Verdict{ - "clojure": {"stdin.eof": W}, + "clojure": nil, "babashka": nil, "clojure-clr": checktest.Merged(noJava, map[string]check.Verdict{"op.unknown-echo": W, "session.across-connections": W, "session.isolated": F, "session.unknown": F, "session.closed": F, "stdin.need-input": S, @@ -154,7 +154,7 @@ func TestPresetsMatchTheScenarios(t *testing.T) { // but the profile of nREPL itself is named after Clojure. server, _, _ := strings.Cut(prof.Name, " (") if p.Name == "clojure" { - server = "nREPL 1.7.0" + server = "nREPL 1.8.0" } for _, s := range Scenarios() { if slices.Contains(p.Scenarios, s.Name) && !strings.Contains(s.Who, server) { diff --git a/internal/serve/scenarios.go b/internal/serve/scenarios.go index 834f8e9..a28466f 100644 --- a/internal/serve/scenarios.go +++ b/internal/serve/scenarios.go @@ -86,7 +86,7 @@ var catalog = []scenario{ {Scenario{"ns-error", "An eval in a namespace that doesn't exist fails without namespace-not-found", "Basilisp"}, func(b *behavior) { b.nsError = true }, []string{"basilisp"}}, {Scenario{"eof-error", "Reading past the end of input fails instead of returning nil", "nREPL 1.7.0"}, - func(b *behavior) { b.eofError = true }, []string{"clojure"}}, + func(b *behavior) { b.eofError = true }, nil}, {Scenario{"unsorted-keys", "The keys of reply dicts aren't sorted", "jank"}, func(b *behavior) { b.unsortedKeys = true }, []string{"jank"}}, {Scenario{"byte-writes", "Replies are written a byte at a time", "any server, as TCP can deliver a message in pieces"}, diff --git a/profiles/clojure.toml b/profiles/clojure.toml index b92173b..eb2347e 100644 --- a/profiles/clojure.toml +++ b/profiles/clojure.toml @@ -1,9 +1,9 @@ # The reference implementation, nrepl/nrepl, on the Clojure CLI. -name = "Clojure (nrepl/nrepl 1.7.0)" +name = "Clojure (nrepl/nrepl 1.8.0)" homepage = "https://github.com/nrepl/nrepl" [launch] -command = ["clojure", "-Sdeps", "{:deps {nrepl/nrepl {:mvn/version \"1.7.0\"}}}", "-M", "-m", "nrepl.cmdline", "--port", "0"] +command = ["clojure", "-Sdeps", "{:deps {nrepl/nrepl {:mvn/version \"1.8.0\"}}}", "-M", "-m", "nrepl.cmdline", "--port", "0"] port-pattern = 'nREPL server started on port (\d+)' startup-timeout = "120s"