Skip to content

Upgrade to Nim 2.2.10, bump ecosystem deps, add nimpretty formatter#412

Open
karolyi wants to merge 1 commit into
nim-lang:masterfrom
karolyi:master
Open

Upgrade to Nim 2.2.10, bump ecosystem deps, add nimpretty formatter#412
karolyi wants to merge 1 commit into
nim-lang:masterfrom
karolyi:master

Conversation

@karolyi
Copy link
Copy Markdown

@karolyi karolyi commented May 18, 2026

Nim / dependency versions:

  • Nim: 2.0.8 → 2.2.10; chronos: 4.0.4 → 4.2.2; json_rpc: 0.5.0 → 0.6.0
  • chronicles: 0.10.3 → 0.12.2; serialization: 0.2.6 → 0.5.2
  • json_serialization: 0.2.9 → 0.4.4; stew: 0.2.0 → 0.5.0
  • faststreams: 0.3.0 → 0.5.1; websock: 0.2.0 → 0.3.0; plus patch bumps for bearssl, nimcrypto, stint, zlib, httputils, testutils, unittest2, regex

Compatibility fixes for new library APIs:

  • json_rpc 0.6: req.meth → req.method.get(""); decoder takes string not JsonNode; inject null id for LSP notifications, null→{} for null params
  • chronicles 0.12: chronicles_disable_thread_id → chronicles_thread_ids=no; JsonNode log args wrapped in $() throughout all call sites
  • Nim 2.2 gcsafe: add {.gcsafe.} to addCallback closures in ls.nim
  • nimble dump: pass workingDir instead of file path as CLI argument
  • lspsocketclient: manual requestId counter, rename address→connAddress, fix error-cleanup loop to use responses table, tighten proc pragmas

Formatter improvements (routes/lsp.nim, ls.nim):

  • Add NlsFormatter enum and nim.formatter config option (nph | nimpretty)
  • Add getNimprettyPath/getFormatterPath helpers; advertise documentFormattingProvider when either formatter binary is on PATH
  • Fix pipe-full deadlock in format proc: drain stderr concurrently before waiting for process exit; add defer closeWait for process cleanup
  • Fix CancelledError path: kill + reap zombie process before returning
  • Fix typos in log strings (doenst, formating, modifyed)

Add test_hover.py: standalone Python LSP smoke-test script

@karolyi
Copy link
Copy Markdown
Author

karolyi commented May 18, 2026

Nim 2.2.x and Dependency Upgrades

This document details all changes made to support Nim 2.2.10 and the newest
versions of the status-im / nim ecosystem libraries.

Nim Version

Before After
Nim == 2.0.8 >= 2.2.10

Dependency Versions

Package Before After
chronos 4.0.4 4.2.2
json_rpc 0.5.0 0.6.0
chronicles 0.10.3 0.12.2
serialization 0.2.6 0.5.2
json_serialization 0.2.9 0.4.4
stew 0.2.0 0.5.0
faststreams 0.3.0 0.5.1
bearssl 0.2.5 0.2.8
nimcrypto 0.6.2 0.7.3
stint 0.8.1 0.8.2
zlib 0.1.0 0.2.0
websock 0.2.0 0.3.0
httputils 0.3.0 0.4.1
testutils 0.5.3 0.8.1
unittest2 0.2.4 0.2.5
regex 0.26.1 0.26.3
with 0.5.0 0.5.0 (explicit pin)

New transitive dependencies added to nimble.lock: zlib (now a first-class
lock entry), httputils (promoted), regex and unicodedb (from nitely).
All packages now declare nim as an explicit dependency in the lock file.

Source Code Changes

nimlangserver.nimble

Version constraints changed from strict equality to lower-bound ranges so the
project can build against any compatible future patch release.

nim.cfg / tests/all.nim.cfg / tests/config.nims

chronicles 0.12.x renamed the thread-id compile flag:

# Before
--define:"chronicles_disable_thread_id"

# After
--define:"chronicles_thread_ids=no"

The old flag was silently ignored in newer chronicles, causing unexpected
thread-id output in log lines. The new flag is the canonical switch.

ls.nim

nimble dump invocation (getNimbleDumpInfo):

The dump subcommand no longer accepts a file path as an argument in newer
Nimble. The working directory is now set explicitly instead:

# Before
arguments = @["dump", nimbleFile]

# After
workingDir = nimbleFile.parentDir(),
arguments = @["dump"],

{.gcsafe.} on addCallback closures:

Nim 2.2 enforces gcsafe more strictly in async callback context. Two
callbacks gained the pragma to satisfy the new check:

  • projectNext.ns.addCallback do(fut: Future[Nimsuggest]) {.gcsafe.}:
  • ls.workspaceConfiguration.addCallback do(futConfiguration: Future[JsonNode]) {.gcsafe.}:

$ conversion in chronicles log calls:

chronicles 0.12 no longer accepts arbitrary JsonNode values as log
arguments; they must be converted to string first:

# Before
res = res.read()
configuration = futConfiguration.read()

# After
res = $res.read()
configuration = $futConfiguration.read()

lstransports.nim

json_rpc 0.6 API: req.methreq.method.get("")

The RequestRx type renamed meth to method and made it an Option[string]
in json_rpc 0.6. All call sites updated:

# Before
req.meth
# After
req.method.get("")

contentJson["method"] logging fix:

# Before
debug "[Processing Message]", request = contentJson["method"]
# After
debug "[Processing Message]", request = contentJson["method"].getStr()

LSP null-params and notification-id compatibility (json_rpc 0.6 decoder is
stricter):

The LSP spec allows "params": null and notifications without an "id" field.
json_rpc 0.6's decoder requires params to be an object/array and id to be
present on RequestRx. Two fixups are applied before decoding:

# Replace null params with empty object
if contentJson.getOrDefault("params").kind == JNull:
  contentJson["params"] = newJObject()

# Inject a null id for notifications so RequestRx.id decodes cleanly
if "id" notin contentJson:
  contentJson["id"] = newJNull()

var req = JrpcSys.decode($contentJson, RequestRx)

The re-serialisation ($contentJson) is required because JrpcSys.decode in
0.6 takes a string, not a JsonNode.

routes/lsp.nim

didChangeConfiguration log call: conf (a JsonNode) → $conf (string),
same chronicles 0.12 requirement as above.

tests/lspsocketclient.nim

Manual request-ID counter replaces the removed getNextId() helper from
json_rpc. A new requestId: int field is added to LspSocketClient and
incremented manually:

client.requestId += 1
let id = client.requestId
reqJson["id"] = %id          # was %id.num
client.responses[id] = newFut

Field rename: addressconnAddress avoids shadowing a stdlib symbol
that caused a compile error under Nim 2.2:

client.connAddress = addresses[0]   # was client.address

Error-cleanup loop updated to use the correct responses table (was
incorrectly referencing the removed awaiting and batchFut fields):

for _, fut in client.responses:
  if not fut.finished:
    fut.fail(localException)
client.responses.clear()

notificationHandle pragma tightened to match the stricter async
dispatcher signature requirements in chronos 4.2:

proc notificationHandle(...): Future[void] {.gcsafe, raises: [].}

except CatchableErrorexcept Exception in the same proc to correctly
silence all exceptions including Defects that may be raised inside Nim 2.2
string formatting.

$ on JsonNode log arguments throughout the file (same chronicles 0.12
requirement).

Formatter Support (routes/lsp.nim, ls.nim)

nimpretty as an alternative formatter

A new NlsFormatter enum and nim.formatter config option let users choose
between nph (default) and nimpretty:

type NlsFormatter* = enum
  nfNph = "nph"
  nfNimpretty = "nimpretty"

# NlsConfig gains:
formatter*: Option[NlsFormatter]

documentFormattingProvider is now advertised as true when either
formatter binary is found on $PATH, not only nph.

Pipe-full deadlock fix and CancelledError cleanup in format

The old format proc called process.waitForExit before reading stderr,
which could deadlock if the formatter wrote enough output to fill the pipe
buffer. The new implementation drains stderr concurrently:

defer: await noCancel(process.closeWait())
let stderrFuture = process.stderrStream.read()   # start draining immediately
let res =
  try:
    await process.waitForExit(InfiniteDuration)
  except CancelledError:
    # waitForExit cancels the SIGCHLD watcher without calling waitpid —
    # kill explicitly and reap the zombie before returning.
    discard process.kill()
    discard await noCancel(stderrFuture)
    try: discard await noCancel(process.waitForExit(InfiniteDuration))
    except CatchableError: discard
    return none(TextEdit)
let stderrOutput = string.fromBytes(await stderrFuture)

The defer: await noCancel(process.closeWait()) ensures the process handle is
always released, even on error paths.

Typo fixes

"doenst""doesn't", "formating""formatting",
"modifyed""modified" in log/message strings.

New File: test_hover.py

A standalone Python 3 manual-testing script that:

  1. Spawns nimlangserver on a socket port.
  2. Performs a full LSP handshake (initializeinitializedtextDocument/didOpen).
  3. Waits for the window/showMessage "Nimsuggest initialized" notification.
  4. Issues a textDocument/hover request and prints the result.

Useful for quickly verifying that the server starts, nimsuggest initialises, and
hover responses are returned correctly without running the full test suite.

python3 test_hover.py
python3 test_hover.py --port 19999 --line 5 --char 12 --timeout 90

@karolyi
Copy link
Copy Markdown
Author

karolyi commented May 18, 2026

I've spent a whole evening with this after getting frustrated that the langserver wasn't really functioning as intended. As it turned out, this was the crucial fix:

+      # LSP allows null or absent params; json_rpc 0.6+ decoder requires array/object
+      if contentJson.getOrDefault("params").kind == JNull:
+        contentJson["params"] = newJObject()
+      # Notifications have no id; RequestRx.id is non-optional so inject null
+      # to avoid raiseIncompleteObject during decode.
+      if "id" notin contentJson:
+        contentJson["id"] = newJNull()

This made the entire langserver smooth again and my sublime text also doesn't go crazy anymore.

@jmgomez
Copy link
Copy Markdown
Collaborator

jmgomez commented May 20, 2026

Thanks for your work. However before we can merge this a few changes has to be made:

  • Split the PRs (fixes, updating, test coverage, formatter)
  • Follow the convention for the new filenames (test_ prefix should be just t)
  • Make sure CI is green

@karolyi
Copy link
Copy Markdown
Author

karolyi commented May 20, 2026

Before I dissect the changes to separate commits, I need a test run which I can't start here.

@karolyi
Copy link
Copy Markdown
Author

karolyi commented May 29, 2026

Need another test run but good to go afaik. The "ubuntu hash mismatch" error is nothing I can make sense of. I'm on Artix linux (on this machine), and nimble wrote these hashes.

@karolyi
Copy link
Copy Markdown
Author

karolyi commented May 29, 2026

I've tried to satisfy the ubuntu test env and modified the checksum to be the one it expects, but now I have another error. People on IRC told me it's more of a ubuntu related problem now, so I'm not sure how to proceed.

Suggestions are welcome.

Nim / dependency versions:
- Nim: 2.0.8 → 2.2.10; chronos: 4.0.4 → 4.2.2; json_rpc: 0.5.0 → 0.6.0
- chronicles: 0.10.3 → 0.12.2; serialization: 0.2.6 → 0.5.2
- json_serialization: 0.2.9 → 0.4.4; stew: 0.2.0 → 0.5.0
- faststreams: 0.3.0 → 0.5.1; websock: 0.2.0 → 0.3.0; plus patch bumps for
  bearssl, nimcrypto, stint, zlib, httputils, testutils, unittest2, regex

Compatibility fixes for new library APIs:
- json_rpc 0.6: req.meth → req.method.get(""); decoder takes string not
  JsonNode; inject null id for LSP notifications, null→{} for null params
- chronicles 0.12: chronicles_disable_thread_id → chronicles_thread_ids=no;
  JsonNode log args wrapped in $() throughout all call sites
- Nim 2.2 gcsafe: add {.gcsafe.} to addCallback closures in ls.nim
- nimble dump: pass workingDir instead of file path as CLI argument
- lspsocketclient: manual requestId counter, rename address→connAddress,
  fix error-cleanup loop to use responses table, tighten proc pragmas

Formatter improvements (routes/lsp.nim, ls.nim):
- Add NlsFormatter enum and nim.formatter config option (nph | nimpretty)
- Add getNimprettyPath/getFormatterPath helpers; advertise
  documentFormattingProvider when either formatter binary is on PATH
- Fix pipe-full deadlock in format proc: drain stderr concurrently before
  waiting for process exit; add defer closeWait for process cleanup
- Fix CancelledError path: kill + reap zombie process before returning
- Fix typos in log strings (doenst, formating, modifyed)

Add test_hover.py: standalone Python LSP smoke-test script
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants